@gitlab/at.js 1.5.6 → 1.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @gitlab/at.js - 1.5.6
3
- * Copyright (c) 2021 chord.luo <chord.luo@gmail.com>;
2
+ * @gitlab/at.js - 1.5.8
3
+ * Copyright (c) 2026 chord.luo <chord.luo@gmail.com>;
4
4
  * Homepage: https://gitlab.com/gitlab-org/frontend/At.js
5
5
  * License: MIT
6
6
  */
@@ -37,20 +37,48 @@ KEY_CODE = {
37
37
  SPACE: 32
38
38
  };
39
39
 
40
+ // Functions set for handling and rendering the data.
41
+ // Others developers can override these methods to tweak At.js such as matcher.
42
+ // We can override them in `callbacks` settings.
43
+
44
+ // @mixin
45
+
46
+ // The context of these functions is `$.atwho.Controller` object and they are called in this sequences:
47
+
48
+ // [beforeSave, matcher, filter, remoteFilter, sorter, tplEvl, highlighter, beforeInsert, afterMatchFailed]
49
+
40
50
  DEFAULT_CALLBACKS = {
51
+ // It would be called to restructure the data before At.js invokes `Model#save` to save data
52
+ // By default, At.js will convert it to a Hash Array.
53
+
54
+ // @param data [Array] data to refacotor.
55
+ // @return [Array] Data after refactor.
41
56
  beforeSave: function(data) {
42
57
  return Controller.arrayToDefaultHash(data);
43
58
  },
59
+ // It would be called to match the `flag`.
60
+ // It will match at start of line or after whitespace
61
+
62
+ // @param flag [String] current `flag` ("@", etc)
63
+ // @param subtext [String] Text from start to current caret position.
64
+ // @param should_startWithSpace [boolean] accept white space as beginning of match.
65
+ // @param acceptSpaceBar [boolean] accept a space bar in the center of match,
66
+ // so you can match a first and last name, for ex.
67
+
68
+ // @return [String | null] Matched result.
44
69
  matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
45
70
  var _a, _y, match, regexp, space;
71
+ // escape RegExp
46
72
  flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
47
73
  if (should_startWithSpace) {
48
74
  flag = '(?:^|\\s)' + flag;
49
75
  }
76
+ // À
50
77
  _a = decodeURI("%C3%80");
78
+ // ÿ
51
79
  _y = decodeURI("%C3%BF");
52
80
  space = acceptSpaceBar ? "\ " : "";
53
- regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
81
+ regexp = new RegExp(`${flag}([A-Za-z${_a}-${_y}0-9_${space}\'\.\+\-]*)$|${flag}([^\\x00-\\xff]*)$`, 'gi');
54
82
  match = regexp.exec(subtext);
55
83
  if (match) {
56
84
  return match[2] || match[1];
@@ -58,8 +86,18 @@ DEFAULT_CALLBACKS = {
58
86
  return null;
59
87
  }
60
88
  },
89
+ // ---------------------
90
+
91
+ // Filter data by matched string.
92
+
93
+ // @param query [String] Matched string.
94
+ // @param data [Array] data list
95
+ // @param searchKey [String] at char for searching.
96
+
97
+ // @return [Array] result data.
61
98
  filter: function(query, data, searchKey) {
62
99
  var _results, i, item, len;
100
+ // !!null #=> false; !!undefined #=> false; !!'' #=> false;
63
101
  _results = [];
64
102
  for (i = 0, len = data.length; i < len; i++) {
65
103
  item = data[i];
@@ -69,7 +107,24 @@ DEFAULT_CALLBACKS = {
69
107
  }
70
108
  return _results;
71
109
  },
110
+ // If a function is given, At.js will invoke it if local filter can not find any data
111
+
112
+ // @param params [String] matched query
113
+ // @param callback [Function] callback to render page.
72
114
  remoteFilter: null,
115
+ // remoteFilter: (query, callback) ->
116
+ // $.ajax url,
117
+ // data: params
118
+ // success: (data) ->
119
+ // callback(data)
120
+
121
+ // Sorter data of course.
122
+
123
+ // @param query [String] matched string
124
+ // @param items [Array] data that was refactored
125
+ // @param searchKey [String] at char to search
126
+
127
+ // @return [Array] sorted data
73
128
  sorter: function(query, items, searchKey) {
74
129
  var _results, i, item, len;
75
130
  if (!query) {
@@ -87,6 +142,12 @@ DEFAULT_CALLBACKS = {
87
142
  return a.atwho_order - b.atwho_order;
88
143
  });
89
144
  },
145
+ // Evaluate the template either as a string or as a function
146
+ // this allows someone to pass in a set of data that needs a
147
+ // different template for different data results
148
+
149
+ // @param tpl [function] the template function or string
150
+ // @param map [Hash] Data map to eval.
90
151
  tplEval: function(tpl, map) {
91
152
  var error, template;
92
153
  template = tpl;
@@ -102,6 +163,12 @@ DEFAULT_CALLBACKS = {
102
163
  return "";
103
164
  }
104
165
  },
166
+ // Highlight the `matched query` string.
167
+
168
+ // @param li [String] HTML String after eval.
169
+ // @param query [String] matched query.
170
+
171
+ // @return [String] highlighted string.
105
172
  highlighter: function(li, query) {
106
173
  var regexp;
107
174
  if (!query) {
@@ -112,19 +179,33 @@ DEFAULT_CALLBACKS = {
112
179
  return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
113
180
  });
114
181
  },
182
+ // What to do before inserting item's value into inputor.
183
+
184
+ // @param value [String] content to insert
185
+ // @param $li [jQuery Object] the chosen item
186
+ // @param e [event Object] from the user selection (keyDown or click)
115
187
  beforeInsert: function(value, $li, e) {
116
188
  return value;
117
189
  },
190
+ // You can adjust the menu's offset here.
191
+
192
+ // @param offset [Hash] offset will be applied to menu
193
+ // beforeReposition: (offset) ->
194
+ // offset.left += 10
195
+ // offset.top += 10
196
+ // offset
118
197
  beforeReposition: function(offset) {
119
198
  return offset;
120
199
  },
121
200
  afterMatchFailed: function(at, el) {}
122
201
  };
123
202
 
203
+ // At.js central contoller(searching, matching, evaluating and rendering.)
124
204
  var App;
125
205
 
126
- App = (function() {
127
- function App(inputor) {
206
+ App = class App {
207
+ // @param inputor [HTML DOM Object] `input` or `textarea`
208
+ constructor(inputor) {
128
209
  this.currentFlag = null;
129
210
  this.controllers = {};
130
211
  this.aliasMaps = {};
@@ -133,19 +214,16 @@ App = (function() {
133
214
  this.listen();
134
215
  }
135
216
 
136
- App.prototype.createContainer = function(doc) {
217
+ createContainer(doc) {
137
218
  var ref;
138
219
  if ((ref = this.$el) != null) {
139
220
  ref.remove();
140
221
  }
141
222
  return $(doc.body).append(this.$el = $("<div class='atwho-container'></div>"));
142
- };
223
+ }
143
224
 
144
- App.prototype.setupRootElement = function(iframe, asRoot) {
225
+ setupRootElement(iframe, asRoot = false) {
145
226
  var error;
146
- if (asRoot == null) {
147
- asRoot = false;
148
- }
149
227
  if (iframe) {
150
228
  this.window = iframe.contentWindow;
151
229
  this.document = iframe.contentDocument || this.window.document;
@@ -159,14 +237,16 @@ App = (function() {
159
237
  error = error1;
160
238
  this.iframe = null;
161
239
  if ($.fn.atwho.debug) {
162
- throw new Error("iframe auto-discovery is failed.\nPlease use `setIframe` to set the target iframe manually.\n" + error);
240
+ throw new Error(`iframe auto-discovery is failed.
241
+ Please use \`setIframe\` to set the target iframe manually.
242
+ ${error}`);
163
243
  }
164
244
  }
165
245
  }
166
246
  return this.createContainer((this.iframeAsRoot = asRoot) ? this.document : document);
167
- };
247
+ }
168
248
 
169
- App.prototype.controller = function(at) {
249
+ controller(at) {
170
250
  var c, current, currentFlag, ref;
171
251
  if (this.aliasMaps[at]) {
172
252
  current = this.controllers[this.aliasMaps[at]];
@@ -185,81 +265,76 @@ App = (function() {
185
265
  } else {
186
266
  return this.controllers[this.currentFlag];
187
267
  }
188
- };
268
+ }
189
269
 
190
- App.prototype.setContextFor = function(at) {
270
+ setContextFor(at) {
191
271
  this.currentFlag = at;
192
272
  return this;
193
- };
273
+ }
194
274
 
195
- App.prototype.reg = function(flag, setting) {
275
+ // At.js can register multiple at char (flag) to every inputor such as "@" and ":"
276
+ // Along with their own `settings` so that it works differently.
277
+ // After register, we still can update their `settings` such as updating `data`
278
+
279
+ // @param flag [String] at char (flag)
280
+ // @param settings [Hash] the settings
281
+ reg(flag, setting) {
196
282
  var base, controller;
197
283
  controller = (base = this.controllers)[flag] || (base[flag] = this.$inputor.is('[contentEditable]') ? new EditableController(this, flag) : new TextareaController(this, flag));
198
284
  if (setting.alias) {
285
+ // TODO: it will produce rubbish alias map, reduse this.
199
286
  this.aliasMaps[setting.alias] = flag;
200
287
  }
201
288
  controller.init(setting);
202
289
  return this;
203
- };
290
+ }
204
291
 
205
- App.prototype.listen = function() {
206
- return this.$inputor.on('compositionstart', (function(_this) {
207
- return function(e) {
208
- var ref;
209
- if ((ref = _this.controller()) != null) {
210
- ref.view.hide();
211
- }
212
- _this.isComposing = true;
213
- return null;
214
- };
215
- })(this)).on('compositionend', (function(_this) {
216
- return function(e) {
217
- _this.isComposing = false;
218
- setTimeout(function(e) {
219
- return _this.dispatch(e);
220
- });
221
- return null;
222
- };
223
- })(this)).on('keyup.atwhoInner', (function(_this) {
224
- return function(e) {
225
- return _this.onKeyup(e);
226
- };
227
- })(this)).on('keydown.atwhoInner', (function(_this) {
228
- return function(e) {
229
- return _this.onKeydown(e);
230
- };
231
- })(this)).on('blur.atwhoInner', (function(_this) {
232
- return function(e) {
233
- var c;
234
- if (c = _this.controller()) {
235
- c.expectedQueryCBId = null;
236
- return c.view.hide(e, c.getOpt("displayTimeout"));
237
- }
238
- };
239
- })(this)).on('click.atwhoInner', (function(_this) {
240
- return function(e) {
241
- return _this.dispatch(e);
242
- };
243
- })(this)).on('scroll.atwhoInner', (function(_this) {
244
- return function() {
245
- var lastScrollTop;
246
- lastScrollTop = _this.$inputor.scrollTop();
247
- return function(e) {
248
- var currentScrollTop, ref;
249
- currentScrollTop = e.target.scrollTop;
250
- if (lastScrollTop !== currentScrollTop) {
251
- if ((ref = _this.controller()) != null) {
252
- ref.view.hide(e);
253
- }
292
+ // binding jQuery events of `inputor`'s
293
+ listen() {
294
+ return this.$inputor.on('compositionstart', (e) => {
295
+ var ref;
296
+ if ((ref = this.controller()) != null) {
297
+ ref.view.hide();
298
+ }
299
+ this.isComposing = true;
300
+ return null;
301
+ }).on('compositionend', (e) => {
302
+ this.isComposing = false;
303
+ setTimeout((e) => {
304
+ return this.dispatch(e);
305
+ });
306
+ return null;
307
+ }).on('keyup.atwhoInner', (e) => {
308
+ return this.onKeyup(e);
309
+ }).on('keydown.atwhoInner', (e) => {
310
+ return this.onKeydown(e);
311
+ }).on('blur.atwhoInner', (e) => {
312
+ var c;
313
+ if (c = this.controller()) {
314
+ c.expectedQueryCBId = null;
315
+ return c.view.hide(e, c.getOpt("displayTimeout"));
316
+ }
317
+ }).on('click.atwhoInner', (e) => {
318
+ return this.dispatch(e);
319
+ }).on('scroll.atwhoInner', (() => {
320
+ var lastScrollTop;
321
+ // make returned handler handle the very first call properly
322
+ lastScrollTop = this.$inputor.scrollTop();
323
+ return (e) => {
324
+ var currentScrollTop, ref;
325
+ currentScrollTop = e.target.scrollTop;
326
+ if (lastScrollTop !== currentScrollTop) {
327
+ if ((ref = this.controller()) != null) {
328
+ ref.view.hide(e);
254
329
  }
255
- lastScrollTop = currentScrollTop;
256
- return true;
257
- };
330
+ }
331
+ lastScrollTop = currentScrollTop;
332
+ return true; // ensure we don't stop bubbling
258
333
  };
259
- })(this)());
260
- };
334
+ })());
335
+ }
261
336
 
262
- App.prototype.shutdown = function() {
337
+ shutdown() {
263
338
  var _, c, ref;
264
339
  ref = this.controllers;
265
340
  for (_ in ref) {
@@ -269,9 +344,9 @@ App = (function() {
269
344
  }
270
345
  this.$inputor.off('.atwhoInner');
271
346
  return this.$el.remove();
272
- };
347
+ }
273
348
 
274
- App.prototype.dispatch = function(e) {
349
+ dispatch(e) {
275
350
  var _, c, ref, results;
276
351
  if (void 0 === e) {
277
352
  return;
@@ -283,9 +358,9 @@ App = (function() {
283
358
  results.push(c.lookUp(e));
284
359
  }
285
360
  return results;
286
- };
361
+ }
287
362
 
288
- App.prototype.onKeyup = function(e) {
363
+ onKeyup(e) {
289
364
  var ref;
290
365
  switch (e.keyCode) {
291
366
  case KEY_CODE.ESC:
@@ -309,10 +384,12 @@ App = (function() {
309
384
  default:
310
385
  this.dispatch(e);
311
386
  }
312
- };
387
+ }
313
388
 
314
- App.prototype.onKeydown = function(e) {
389
+ // coffeescript will return everywhere!!
390
+ onKeydown(e) {
315
391
  var ref, view;
392
+ // return if not (view = this.controller().view).visible()
316
393
  view = (ref = this.controller()) != null ? ref.view : void 0;
317
394
  if (!(view && view.visible())) {
318
395
  return;
@@ -366,21 +443,18 @@ App = (function() {
366
443
  default:
367
444
  $.noop();
368
445
  }
369
- };
370
-
371
- return App;
446
+ }
372
447
 
373
- })();
448
+ };
374
449
 
375
- var Controller,
376
- slice = [].slice;
450
+ var Controller;
377
451
 
378
- Controller = (function() {
379
- Controller.prototype.uid = function() {
452
+ Controller = class Controller {
453
+ uid() {
380
454
  return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime());
381
- };
455
+ }
382
456
 
383
- function Controller(app, at1) {
457
+ constructor(app, at1) {
384
458
  this.app = app;
385
459
  this.at = at1;
386
460
  this.$inputor = this.app.$inputor;
@@ -390,53 +464,71 @@ Controller = (function() {
390
464
  this.query = null;
391
465
  this.pos = 0;
392
466
  this.range = null;
393
- if ((this.$el = $("#atwho-ground-" + this.id, this.app.$el)).length === 0) {
394
- this.app.$el.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
467
+ if ((this.$el = $(`#atwho-ground-${this.id}`, this.app.$el)).length === 0) {
468
+ this.app.$el.append(this.$el = $(`<div id='atwho-ground-${this.id}'></div>`));
395
469
  }
396
470
  this.model = new Model(this);
397
471
  this.view = new View(this);
398
472
  }
399
473
 
400
- Controller.prototype.init = function(setting) {
401
- this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
474
+ init(setting) {
475
+ this.setting = $.extend({}, this.setting || $.fn.atwho.default, setting);
402
476
  this.view.init();
403
477
  return this.model.reload(this.setting.data);
404
- };
478
+ }
405
479
 
406
- Controller.prototype.destroy = function() {
480
+ destroy() {
407
481
  this.trigger('beforeDestroy');
408
482
  this.model.destroy();
409
483
  this.view.destroy();
410
484
  return this.$el.remove();
411
- };
485
+ }
412
486
 
413
- Controller.prototype.callDefault = function() {
414
- var args, error, funcName;
415
- funcName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
487
+ callDefault(funcName, ...args) {
488
+ var error;
416
489
  try {
417
490
  return DEFAULT_CALLBACKS[funcName].apply(this, args);
418
491
  } catch (error1) {
419
492
  error = error1;
420
- return $.error(error + " Or maybe At.js doesn't have function " + funcName);
493
+ return $.error(`${error} Or maybe At.js doesn't have function ${funcName}`);
421
494
  }
422
- };
495
+ }
496
+
497
+ // Delegate custom `jQueryEvent` to the inputor
498
+ // This function will add `atwho` as namespace to every jQuery event
499
+ // and pass current context as the last param to it.
423
500
 
424
- Controller.prototype.trigger = function(name, data) {
501
+ // @example
502
+ // this.trigger "roll_n_rock", [1,2,3,4]
503
+
504
+ // $inputor.on "rool_n_rock", (e, one, two, three, four) ->
505
+ // console.log one, two, three, four
506
+
507
+ // @param name [String] Event name
508
+ // @param data [Array] data to callback
509
+ trigger(name, data = []) {
425
510
  var alias, eventName;
426
- if (data == null) {
427
- data = [];
428
- }
429
511
  data.push(this);
430
512
  alias = this.getOpt('alias');
431
- eventName = alias ? name + "-" + alias + ".atwho" : name + ".atwho";
513
+ eventName = alias ? `${name}-${alias}.atwho` : `${name}.atwho`;
432
514
  return this.$inputor.trigger(eventName, data);
433
- };
515
+ }
516
+
517
+ // Get callback either in settings which was set by plugin user or in default callbacks list.
434
518
 
435
- Controller.prototype.callbacks = function(funcName) {
519
+ // @param funcName [String] callback's name
520
+ // @return [Function] The callback.
521
+ callbacks(funcName) {
436
522
  return this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName];
437
- };
523
+ }
524
+
525
+ // Because different registered at chars have different settings.
526
+ // so we should give their own for them.
438
527
 
439
- Controller.prototype.getOpt = function(at, default_value) {
528
+ // @param at [String] setting's at name
529
+ // @param default_value [?] return this if nothing is returned from current settings.
530
+ // @return [?] setting's value
531
+ getOpt(at, default_value) {
440
532
  var e;
441
533
  try {
442
534
  return this.setting[at];
@@ -444,25 +536,28 @@ Controller = (function() {
444
536
  e = error1;
445
537
  return null;
446
538
  }
447
- };
539
+ }
448
540
 
449
- Controller.prototype.insertContentFor = function($li) {
541
+ insertContentFor($li) {
450
542
  var data, tpl;
451
543
  tpl = this.getOpt('insertTpl');
452
544
  data = $.extend({}, $li.data('item-data'), {
453
545
  'atwho-at': this.at
454
546
  });
455
547
  return this.callbacks("tplEval").call(this, tpl, data, "onInsert");
456
- };
548
+ }
457
549
 
458
- Controller.prototype.renderView = function(data) {
550
+ // Render list view
551
+
552
+ // @param data [Array] The data
553
+ renderView(data) {
459
554
  var searchKey;
460
555
  searchKey = this.getOpt("searchKey");
461
556
  data = this.callbacks("sorter").call(this, this.query.text, data, searchKey);
462
557
  return this.view.render(data.slice(0, this.getOpt('limit')));
463
- };
558
+ }
464
559
 
465
- Controller.arrayToDefaultHash = function(data) {
560
+ static arrayToDefaultHash(data) {
466
561
  var i, item, len, results;
467
562
  if (!$.isArray(data)) {
468
563
  return data;
@@ -479,9 +574,10 @@ Controller = (function() {
479
574
  }
480
575
  }
481
576
  return results;
482
- };
577
+ }
483
578
 
484
- Controller.prototype.lookUp = function(e) {
579
+ // Searching!
580
+ lookUp(e) {
485
581
  var query, wait;
486
582
  if (e && e.type === 'click' && !this.getOpt('lookUpOnClick')) {
487
583
  return;
@@ -501,9 +597,9 @@ Controller = (function() {
501
597
  this._lookUp(query);
502
598
  }
503
599
  return query;
504
- };
600
+ }
505
601
 
506
- Controller.prototype._delayLookUp = function(query, wait) {
602
+ _delayLookUp(query, wait) {
507
603
  var now, remaining;
508
604
  now = Date.now ? Date.now() : new Date().getTime();
509
605
  this.previousCallTime || (this.previousCallTime = now);
@@ -511,13 +607,11 @@ Controller = (function() {
511
607
  if ((0 < remaining && remaining < wait)) {
512
608
  this.previousCallTime = now;
513
609
  this._stopDelayedCall();
514
- return this.delayedCallTimeout = setTimeout((function(_this) {
515
- return function() {
516
- _this.previousCallTime = 0;
517
- _this.delayedCallTimeout = null;
518
- return _this._lookUp(query);
519
- };
520
- })(this), wait);
610
+ return this.delayedCallTimeout = setTimeout(() => {
611
+ this.previousCallTime = 0;
612
+ this.delayedCallTimeout = null;
613
+ return this._lookUp(query);
614
+ }, wait);
521
615
  } else {
522
616
  this._stopDelayedCall();
523
617
  if (this.previousCallTime !== now) {
@@ -525,22 +619,23 @@ Controller = (function() {
525
619
  }
526
620
  return this._lookUp(query);
527
621
  }
528
- };
622
+ }
529
623
 
530
- Controller.prototype._stopDelayedCall = function() {
624
+ _stopDelayedCall() {
531
625
  if (this.delayedCallTimeout) {
532
626
  clearTimeout(this.delayedCallTimeout);
533
627
  return this.delayedCallTimeout = null;
534
628
  }
535
- };
629
+ }
536
630
 
537
- Controller.prototype._generateQueryCBId = function() {
631
+ _generateQueryCBId() {
538
632
  return {};
539
- };
633
+ }
540
634
 
541
- Controller.prototype._lookUp = function(query) {
635
+ _lookUp(query) {
542
636
  var _callback;
543
637
  _callback = function(queryCBId, data) {
638
+ // ensure only the latest instance of this function perform actions
544
639
  if (queryCBId !== this.expectedQueryCBId) {
545
640
  return;
546
641
  }
@@ -552,24 +647,17 @@ Controller = (function() {
552
647
  };
553
648
  this.expectedQueryCBId = this._generateQueryCBId();
554
649
  return this.model.query(query.text, $.proxy(_callback, this, this.expectedQueryCBId));
555
- };
556
-
557
- return Controller;
650
+ }
558
651
 
559
- })();
652
+ };
560
653
 
561
- var TextareaController,
562
- 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; },
563
- hasProp = {}.hasOwnProperty;
654
+ var TextareaController;
564
655
 
565
- TextareaController = (function(superClass) {
566
- extend(TextareaController, superClass);
656
+ TextareaController = class TextareaController extends Controller {
657
+ // Catch query string behind the at char
567
658
 
568
- function TextareaController() {
569
- return TextareaController.__super__.constructor.apply(this, arguments);
570
- }
571
-
572
- TextareaController.prototype.catchQuery = function() {
659
+ // @return [Hash] Info of the query. Look likes this: {'text': "hello", 'headPos': 0, 'endPos': 0}
660
+ catchQuery() {
573
661
  var caretPos, content, end, isString, query, start, subtext;
574
662
  content = this.$inputor.val();
575
663
  caretPos = this.$inputor.caret('pos', {
@@ -596,9 +684,12 @@ TextareaController = (function(superClass) {
596
684
  this.view.hide();
597
685
  }
598
686
  return this.query = query;
599
- };
687
+ }
600
688
 
601
- TextareaController.prototype.rect = function() {
689
+ // Get offset of current at char(`flag`)
690
+
691
+ // @return [Hash] the offset which look likes this: {top: y, left: x, bottom: bottom}
692
+ rect() {
602
693
  var c, iframeOffset, scaleBottom;
603
694
  if (!(c = this.$inputor.caret('offset', this.pos - 1, {
604
695
  iframe: this.app.iframe
@@ -616,16 +707,19 @@ TextareaController = (function(superClass) {
616
707
  top: c.top,
617
708
  bottom: c.top + c.height + scaleBottom
618
709
  };
619
- };
710
+ }
620
711
 
621
- TextareaController.prototype.insert = function(content, $li) {
712
+ // Insert value of `data-value` attribute of chosen item into inputor
713
+
714
+ // @param content [String] string to insert
715
+ insert(content, $li) {
622
716
  var $inputor, source, startStr, suffix, text;
623
717
  $inputor = this.$inputor;
624
718
  source = $inputor.val();
625
719
  startStr = source.slice(0, Math.max(this.query.headPos - this.at.length, 0));
626
720
  suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || " ";
627
721
  content += suffix;
628
- text = "" + startStr + content + (source.slice(this.query['endPos'] || 0));
722
+ text = `${startStr}${content}${source.slice(this.query['endPos'] || 0)}`;
629
723
  $inputor.val(text);
630
724
  $inputor.caret('pos', startStr.length + content.length, {
631
725
  iframe: this.app.iframe
@@ -634,35 +728,22 @@ TextareaController = (function(superClass) {
634
728
  $inputor.focus();
635
729
  }
636
730
  return $inputor.change();
637
- };
638
-
639
- return TextareaController;
640
-
641
- })(Controller);
642
-
643
- var EditableController,
644
- 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; },
645
- hasProp = {}.hasOwnProperty;
731
+ }
646
732
 
647
- EditableController = (function(superClass) {
648
- extend(EditableController, superClass);
733
+ };
649
734
 
650
- function EditableController() {
651
- return EditableController.__super__.constructor.apply(this, arguments);
652
- }
735
+ var EditableController;
653
736
 
654
- EditableController.prototype._getRange = function() {
737
+ EditableController = class EditableController extends Controller {
738
+ _getRange() {
655
739
  var sel;
656
740
  sel = this.app.window.getSelection();
657
741
  if (sel.rangeCount > 0) {
658
742
  return sel.getRangeAt(0);
659
743
  }
660
- };
744
+ }
661
745
 
662
- EditableController.prototype._setRange = function(position, node, range) {
663
- if (range == null) {
664
- range = this._getRange();
665
- }
746
+ _setRange(position, node, range = this._getRange()) {
666
747
  if (!(range && node)) {
667
748
  return;
668
749
  }
@@ -676,26 +757,23 @@ EditableController = (function(superClass) {
676
757
  }
677
758
  range.collapse(false);
678
759
  return this._clearRange(range);
679
- };
760
+ }
680
761
 
681
- EditableController.prototype._clearRange = function(range) {
762
+ _clearRange(range = this._getRange()) {
682
763
  var sel;
683
- if (range == null) {
684
- range = this._getRange();
685
- }
686
764
  sel = this.app.window.getSelection();
687
765
  if (this.ctrl_a_pressed == null) {
688
766
  sel.removeAllRanges();
689
767
  return sel.addRange(range);
690
768
  }
691
- };
769
+ }
692
770
 
693
- EditableController.prototype._movingEvent = function(e) {
771
+ _movingEvent(e) {
694
772
  var ref;
695
773
  return e.type === 'click' || ((ref = e.which) === KEY_CODE.RIGHT || ref === KEY_CODE.LEFT || ref === KEY_CODE.UP || ref === KEY_CODE.DOWN);
696
- };
774
+ }
697
775
 
698
- EditableController.prototype._unwrap = function(node) {
776
+ _unwrap(node) {
699
777
  var next;
700
778
  node = $(node).unwrap().get(0);
701
779
  if ((next = node.nextSibling) && next.nodeValue) {
@@ -703,9 +781,9 @@ EditableController = (function(superClass) {
703
781
  $(next).remove();
704
782
  }
705
783
  return node;
706
- };
784
+ }
707
785
 
708
- EditableController.prototype.catchQuery = function(e) {
786
+ catchQuery(e) {
709
787
  var $inserted, $query, _range, index, inserted, isString, lastNode, matched, offset, query, query_content, range;
710
788
  if (!(range = this._getRange())) {
711
789
  return;
@@ -722,6 +800,9 @@ EditableController = (function(superClass) {
722
800
  this._clearRange();
723
801
  return;
724
802
  }
803
+ // absorb range
804
+ // The range at the end of an element is not inside in firefox but not others browsers including IE.
805
+ // To normolize them, we have to move the range inside the element while deleting content or moving caret right after .atwho-inserted
725
806
  if (/firefox/i.test(navigator.userAgent)) {
726
807
  if ($(range.startContainer).is(this.$inputor)) {
727
808
  this._clearRange();
@@ -741,6 +822,7 @@ EditableController = (function(superClass) {
741
822
  }
742
823
  }
743
824
  }
825
+ // modifying inserted element
744
826
  $(range.startContainer).closest('.atwho-inserted').addClass('atwho-query').siblings().removeClass('atwho-query');
745
827
  if (($query = $(".atwho-query", this.app.document)).length > 0 && $query.is(':empty') && $query.text().length === 0) {
746
828
  $query.remove();
@@ -760,6 +842,7 @@ EditableController = (function(superClass) {
760
842
  return;
761
843
  }
762
844
  }
845
+ // matching
763
846
  if ($query.length > 0 && (query_content = $query.attr('data-atwho-at-query'))) {
764
847
  $query.empty().html(query_content).attr('data-atwho-at-query', null);
765
848
  this._setRange('after', $query.get(0), range);
@@ -768,6 +851,7 @@ EditableController = (function(superClass) {
768
851
  _range.setStart(range.startContainer, 0);
769
852
  matched = this.callbacks("matcher").call(this, this.at, _range.toString(), this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
770
853
  isString = typeof matched === 'string';
854
+ // wrapping query with .atwho-query
771
855
  if ($query.length === 0 && isString && (index = range.startOffset - this.at.length - matched.length) >= 0) {
772
856
  range.setStart(range.startContainer, index);
773
857
  $query = $('<span/>', this.app.document).attr(this.getOpt("editableAtwhoQueryAttrs")).addClass('atwho-query');
@@ -786,6 +870,7 @@ EditableController = (function(superClass) {
786
870
  if (isString && matched.length < this.getOpt('minLen', 0)) {
787
871
  return;
788
872
  }
873
+ // handle the matched result
789
874
  if (isString && matched.length <= this.getOpt('maxLen', 20)) {
790
875
  query = {
791
876
  text: matched,
@@ -807,11 +892,16 @@ EditableController = (function(superClass) {
807
892
  }
808
893
  return null;
809
894
  }
810
- };
895
+ }
896
+
897
+ // Get offset of current at char(`flag`)
811
898
 
812
- EditableController.prototype.rect = function() {
899
+ // @return [Hash] the offset which look likes this: {top: y, left: x, bottom: bottom}
900
+ rect() {
813
901
  var $iframe, iframeOffset, rect;
814
902
  rect = this.query.el.offset();
903
+ // do not use {top: 0, left: 0} from jQuery when element is hidden
904
+ // happens every other time the menu is displayed on click in contenteditable
815
905
  if (!(rect && this.query.el[0].getClientRects().length)) {
816
906
  return;
817
907
  }
@@ -822,9 +912,12 @@ EditableController = (function(superClass) {
822
912
  }
823
913
  rect.bottom = rect.top + this.query.el.height();
824
914
  return rect;
825
- };
915
+ }
916
+
917
+ // Insert value of `data-value` attribute of chosen item into inputor
826
918
 
827
- EditableController.prototype.insert = function(content, $li) {
919
+ // @param content [String] string to insert
920
+ insert(content, $li) {
828
921
  var data, overrides, range, suffix, suffixNode;
829
922
  if (!this.$inputor.is(':focus')) {
830
923
  this.$inputor.focus();
@@ -848,30 +941,35 @@ EditableController = (function(superClass) {
848
941
  this.$inputor.focus();
849
942
  }
850
943
  return this.$inputor.change();
851
- };
852
-
853
- return EditableController;
944
+ }
854
945
 
855
- })(Controller);
946
+ };
856
947
 
948
+ // Class to process data
857
949
  var Model;
858
950
 
859
- Model = (function() {
860
- function Model(context) {
951
+ Model = class Model {
952
+ constructor(context) {
861
953
  this.context = context;
862
954
  this.at = this.context.at;
955
+ // NOTE: bind data storage to inputor maybe App class can handle it.
863
956
  this.storage = this.context.$inputor;
864
957
  }
865
958
 
866
- Model.prototype.destroy = function() {
959
+ destroy() {
867
960
  return this.storage.data(this.at, null);
868
- };
961
+ }
869
962
 
870
- Model.prototype.saved = function() {
963
+ saved() {
871
964
  return this.fetch() > 0;
872
- };
965
+ }
873
966
 
874
- Model.prototype.query = function(query, callback) {
967
+ // fetch data from storage by query.
968
+ // will invoke `callback` to return data
969
+
970
+ // @param query [String] catched string for searching
971
+ // @param callback [Function] for receiving data
972
+ query(query, callback) {
875
973
  var _remoteFilter, data, searchKey;
876
974
  data = this.fetch();
877
975
  searchKey = this.context.getOpt("searchKey");
@@ -882,51 +980,66 @@ Model = (function() {
882
980
  } else {
883
981
  return _remoteFilter.call(this.context, query, callback);
884
982
  }
885
- };
983
+ }
886
984
 
887
- Model.prototype.fetch = function() {
985
+ // get or set current data which would be shown on the list view.
986
+
987
+ // @param data [Array] set data
988
+ // @return [Array|undefined] current data that are showing on the list view.
989
+ fetch() {
888
990
  return this.storage.data(this.at) || [];
889
- };
991
+ }
890
992
 
891
- Model.prototype.save = function(data) {
993
+ // save special flag's data to storage
994
+
995
+ // @param data [Array] data to save
996
+ save(data) {
892
997
  return this.storage.data(this.at, this.context.callbacks("beforeSave").call(this.context, data || []));
893
- };
998
+ }
894
999
 
895
- Model.prototype.load = function(data) {
1000
+ // load data. It wouldn't load for a second time if it has been loaded.
1001
+
1002
+ // @param data [Array] data to load
1003
+ load(data) {
896
1004
  if (!(this.saved() || !data)) {
897
1005
  return this._load(data);
898
1006
  }
899
- };
1007
+ }
900
1008
 
901
- Model.prototype.reload = function(data) {
1009
+ reload(data) {
902
1010
  return this._load(data);
903
- };
1011
+ }
904
1012
 
905
- Model.prototype._load = function(data) {
1013
+ // load data from local or remote with callback
1014
+
1015
+ // @param data [Array|String] data to load.
1016
+ _load(data) {
906
1017
  if (typeof data === "string") {
907
1018
  throw new Error("@gitlab/at.js removed support for $.ajax");
908
1019
  } else {
909
1020
  return this.save(data);
910
1021
  }
911
- };
912
-
913
- return Model;
1022
+ }
914
1023
 
915
- })();
1024
+ };
916
1025
 
1026
+ // View class to control how At.js's view showing.
1027
+ // All classes share the same DOM view.
917
1028
  var View;
918
1029
 
919
- View = (function() {
920
- function View(context) {
1030
+ View = class View {
1031
+ // @param controller [Object] The Controller.
1032
+ constructor(context) {
921
1033
  this.context = context;
922
1034
  this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>");
923
1035
  this.$elUl = this.$el.children();
924
1036
  this.timeoutID = null;
1037
+ // create HTML DOM of list view if it does not exist
925
1038
  this.context.$el.append(this.$el);
926
1039
  this.bindEvent();
927
1040
  }
928
1041
 
929
- View.prototype.init = function() {
1042
+ init() {
930
1043
  var header_tpl, id;
931
1044
  id = this.context.getOpt("alias") || this.context.at.charCodeAt(0);
932
1045
  header_tpl = this.context.getOpt("headerTpl");
@@ -934,53 +1047,53 @@ View = (function() {
934
1047
  this.$el.prepend(header_tpl);
935
1048
  }
936
1049
  return this.$el.attr({
937
- 'id': "at-view-" + id
1050
+ 'id': `at-view-${id}`
938
1051
  });
939
- };
1052
+ }
940
1053
 
941
- View.prototype.destroy = function() {
1054
+ destroy() {
942
1055
  return this.$el.remove();
943
- };
1056
+ }
944
1057
 
945
- View.prototype.bindEvent = function() {
1058
+ bindEvent() {
946
1059
  var $menu, lastCoordX, lastCoordY;
947
1060
  $menu = this.$el.find('ul');
948
1061
  lastCoordX = 0;
949
1062
  lastCoordY = 0;
950
- return $menu.on('mousemove.atwho-view', 'li', (function(_this) {
951
- return function(e) {
952
- var $cur;
953
- if (lastCoordX === e.clientX && lastCoordY === e.clientY) {
954
- return;
955
- }
956
- lastCoordX = e.clientX;
957
- lastCoordY = e.clientY;
958
- $cur = $(e.currentTarget);
959
- if ($cur.hasClass('cur')) {
960
- return;
961
- }
962
- $menu.find('.cur').removeClass('cur');
963
- return $cur.addClass('cur');
964
- };
965
- })(this)).on('click.atwho-view', 'li', (function(_this) {
966
- return function(e) {
967
- $menu.find('.cur').removeClass('cur');
968
- $(e.currentTarget).addClass('cur');
969
- _this.choose(e);
970
- return e.preventDefault();
971
- };
972
- })(this));
973
- };
1063
+ return $menu.on('mousemove.atwho-view', 'li', (e) => {
1064
+ var $cur;
1065
+ // If the mouse hasn't actually moved then exit.
1066
+ if (lastCoordX === e.clientX && lastCoordY === e.clientY) {
1067
+ return;
1068
+ }
1069
+ lastCoordX = e.clientX;
1070
+ lastCoordY = e.clientY;
1071
+ $cur = $(e.currentTarget);
1072
+ if ($cur.hasClass('cur')) {
1073
+ return;
1074
+ }
1075
+ $menu.find('.cur').removeClass('cur');
1076
+ return $cur.addClass('cur');
1077
+ }).on('click.atwho-view', 'li', (e) => {
1078
+ $menu.find('.cur').removeClass('cur');
1079
+ $(e.currentTarget).addClass('cur');
1080
+ this.choose(e);
1081
+ return e.preventDefault();
1082
+ });
1083
+ }
974
1084
 
975
- View.prototype.visible = function() {
1085
+ // Check if view is visible
1086
+
1087
+ // @return [Boolean]
1088
+ visible() {
976
1089
  return $.expr.filters.visible(this.$el[0]);
977
- };
1090
+ }
978
1091
 
979
- View.prototype.highlighted = function() {
1092
+ highlighted() {
980
1093
  return this.$el.find(".cur").length > 0;
981
- };
1094
+ }
982
1095
 
983
- View.prototype.choose = function(e) {
1096
+ choose(e) {
984
1097
  var $li, content;
985
1098
  if (($li = this.$el.find(".cur")).length) {
986
1099
  content = this.context.insertContentFor($li);
@@ -992,9 +1105,9 @@ View = (function() {
992
1105
  if (this.context.getOpt("hideWithoutSuffix")) {
993
1106
  return this.stopShowing = true;
994
1107
  }
995
- };
1108
+ }
996
1109
 
997
- View.prototype.reposition = function(rect) {
1110
+ reposition(rect) {
998
1111
  var _window, offset, overflowOffset, ref;
999
1112
  _window = this.context.app.iframeAsRoot ? this.context.app.window : window;
1000
1113
  if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {
@@ -1012,9 +1125,9 @@ View = (function() {
1012
1125
  }
1013
1126
  this.$el.offset(offset);
1014
1127
  return this.context.trigger("reposition", [offset]);
1015
- };
1128
+ }
1016
1129
 
1017
- View.prototype.next = function() {
1130
+ next() {
1018
1131
  var cur, next, nextEl, offset;
1019
1132
  cur = this.$el.find('.cur').removeClass('cur');
1020
1133
  next = cur.next();
@@ -1025,9 +1138,9 @@ View = (function() {
1025
1138
  nextEl = next[0];
1026
1139
  offset = nextEl.offsetTop + nextEl.offsetHeight + (nextEl.nextSibling ? nextEl.nextSibling.offsetHeight : 0);
1027
1140
  return this.scrollTop(Math.max(0, offset - this.$el.height()));
1028
- };
1141
+ }
1029
1142
 
1030
- View.prototype.prev = function() {
1143
+ prev() {
1031
1144
  var cur, offset, prev, prevEl;
1032
1145
  cur = this.$el.find('.cur').removeClass('cur');
1033
1146
  prev = cur.prev();
@@ -1038,13 +1151,13 @@ View = (function() {
1038
1151
  prevEl = prev[0];
1039
1152
  offset = prevEl.offsetTop + prevEl.offsetHeight + (prevEl.nextSibling ? prevEl.nextSibling.offsetHeight : 0);
1040
1153
  return this.scrollTop(Math.max(0, offset - this.$el.height()));
1041
- };
1154
+ }
1042
1155
 
1043
- View.prototype.scrollTop = function(scrollTop) {
1156
+ scrollTop(scrollTop) {
1044
1157
  return this.$elUl.scrollTop(scrollTop);
1045
- };
1158
+ }
1046
1159
 
1047
- View.prototype.show = function() {
1160
+ show() {
1048
1161
  var rect;
1049
1162
  if (this.stopShowing) {
1050
1163
  this.stopShowing = false;
@@ -1058,9 +1171,9 @@ View = (function() {
1058
1171
  if (rect = this.context.rect()) {
1059
1172
  return this.reposition(rect);
1060
1173
  }
1061
- };
1174
+ }
1062
1175
 
1063
- View.prototype.hide = function(e, time) {
1176
+ hide(e, time) {
1064
1177
  var callback;
1065
1178
  if (!this.visible()) {
1066
1179
  return;
@@ -1069,17 +1182,16 @@ View = (function() {
1069
1182
  this.$el.hide();
1070
1183
  return this.context.trigger('hidden', [e]);
1071
1184
  } else {
1072
- callback = (function(_this) {
1073
- return function() {
1074
- return _this.hide();
1075
- };
1076
- })(this);
1185
+ callback = () => {
1186
+ return this.hide();
1187
+ };
1077
1188
  clearTimeout(this.timeoutID);
1078
1189
  return this.timeoutID = setTimeout(callback, time);
1079
1190
  }
1080
- };
1191
+ }
1081
1192
 
1082
- View.prototype.render = function(list) {
1193
+ // render list view
1194
+ render(list) {
1083
1195
  var $li, $ul, i, item, len, li, tpl;
1084
1196
  if (!($.isArray(list) && list.length > 0)) {
1085
1197
  this.hide();
@@ -1102,15 +1214,17 @@ View = (function() {
1102
1214
  if (this.context.getOpt('highlightFirst')) {
1103
1215
  return $ul.find("li:first").addClass("cur");
1104
1216
  }
1105
- };
1106
-
1107
- return View;
1217
+ }
1108
1218
 
1109
- })();
1219
+ };
1110
1220
 
1111
1221
  var Api;
1112
1222
 
1113
1223
  Api = {
1224
+ // load a flag's data
1225
+
1226
+ // @params at[String] the flag
1227
+ // @params data [Array] data to storage.
1114
1228
  load: function(at, data) {
1115
1229
  var c;
1116
1230
  if (c = this.controller(at)) {
@@ -1158,7 +1272,7 @@ $.fn.atwho = function(method) {
1158
1272
  } else if (Api[method] && app) {
1159
1273
  return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
1160
1274
  } else {
1161
- return $.error("Method " + method + " does not exist on jQuery.atwho");
1275
+ return $.error(`Method ${method} does not exist on jQuery.atwho`);
1162
1276
  }
1163
1277
  });
1164
1278
  if (result != null) {
@@ -1168,7 +1282,7 @@ $.fn.atwho = function(method) {
1168
1282
  }
1169
1283
  };
1170
1284
 
1171
- $.fn.atwho["default"] = {
1285
+ $.fn.atwho.default = {
1172
1286
  at: void 0,
1173
1287
  alias: void 0,
1174
1288
  data: null,