@gitlab/at.js 1.5.7 → 1.5.9

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.7
3
- * Copyright (c) 2021 chord.luo <chord.luo@gmail.com>;
2
+ * @gitlab/at.js - 1.5.9
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,73 @@ 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.$el = $('<div></div>');
469
+ this.$el.attr('id', `atwho-ground-${this.id}`);
470
+ this.app.$el.append(this.$el);
395
471
  }
396
472
  this.model = new Model(this);
397
473
  this.view = new View(this);
398
474
  }
399
475
 
400
- Controller.prototype.init = function(setting) {
401
- this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
476
+ init(setting) {
477
+ this.setting = $.extend({}, this.setting || $.fn.atwho.default, setting);
402
478
  this.view.init();
403
479
  return this.model.reload(this.setting.data);
404
- };
480
+ }
405
481
 
406
- Controller.prototype.destroy = function() {
482
+ destroy() {
407
483
  this.trigger('beforeDestroy');
408
484
  this.model.destroy();
409
485
  this.view.destroy();
410
486
  return this.$el.remove();
411
- };
487
+ }
412
488
 
413
- Controller.prototype.callDefault = function() {
414
- var args, error, funcName;
415
- funcName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
489
+ callDefault(funcName, ...args) {
490
+ var error;
416
491
  try {
417
492
  return DEFAULT_CALLBACKS[funcName].apply(this, args);
418
493
  } catch (error1) {
419
494
  error = error1;
420
- return $.error(error + " Or maybe At.js doesn't have function " + funcName);
495
+ return $.error(`${error} Or maybe At.js doesn't have function ${funcName}`);
421
496
  }
422
- };
497
+ }
498
+
499
+ // Delegate custom `jQueryEvent` to the inputor
500
+ // This function will add `atwho` as namespace to every jQuery event
501
+ // and pass current context as the last param to it.
423
502
 
424
- Controller.prototype.trigger = function(name, data) {
503
+ // @example
504
+ // this.trigger "roll_n_rock", [1,2,3,4]
505
+
506
+ // $inputor.on "rool_n_rock", (e, one, two, three, four) ->
507
+ // console.log one, two, three, four
508
+
509
+ // @param name [String] Event name
510
+ // @param data [Array] data to callback
511
+ trigger(name, data = []) {
425
512
  var alias, eventName;
426
- if (data == null) {
427
- data = [];
428
- }
429
513
  data.push(this);
430
514
  alias = this.getOpt('alias');
431
- eventName = alias ? name + "-" + alias + ".atwho" : name + ".atwho";
515
+ eventName = alias ? `${name}-${alias}.atwho` : `${name}.atwho`;
432
516
  return this.$inputor.trigger(eventName, data);
433
- };
517
+ }
518
+
519
+ // Get callback either in settings which was set by plugin user or in default callbacks list.
434
520
 
435
- Controller.prototype.callbacks = function(funcName) {
521
+ // @param funcName [String] callback's name
522
+ // @return [Function] The callback.
523
+ callbacks(funcName) {
436
524
  return this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName];
437
- };
525
+ }
526
+
527
+ // Because different registered at chars have different settings.
528
+ // so we should give their own for them.
438
529
 
439
- Controller.prototype.getOpt = function(at, default_value) {
530
+ // @param at [String] setting's at name
531
+ // @param default_value [?] return this if nothing is returned from current settings.
532
+ // @return [?] setting's value
533
+ getOpt(at, default_value) {
440
534
  var e;
441
535
  try {
442
536
  return this.setting[at];
@@ -444,25 +538,28 @@ Controller = (function() {
444
538
  e = error1;
445
539
  return null;
446
540
  }
447
- };
541
+ }
448
542
 
449
- Controller.prototype.insertContentFor = function($li) {
543
+ insertContentFor($li) {
450
544
  var data, tpl;
451
545
  tpl = this.getOpt('insertTpl');
452
546
  data = $.extend({}, $li.data('item-data'), {
453
547
  'atwho-at': this.at
454
548
  });
455
549
  return this.callbacks("tplEval").call(this, tpl, data, "onInsert");
456
- };
550
+ }
457
551
 
458
- Controller.prototype.renderView = function(data) {
552
+ // Render list view
553
+
554
+ // @param data [Array] The data
555
+ renderView(data) {
459
556
  var searchKey;
460
557
  searchKey = this.getOpt("searchKey");
461
558
  data = this.callbacks("sorter").call(this, this.query.text, data, searchKey);
462
559
  return this.view.render(data.slice(0, this.getOpt('limit')));
463
- };
560
+ }
464
561
 
465
- Controller.arrayToDefaultHash = function(data) {
562
+ static arrayToDefaultHash(data) {
466
563
  var i, item, len, results;
467
564
  if (!$.isArray(data)) {
468
565
  return data;
@@ -479,9 +576,10 @@ Controller = (function() {
479
576
  }
480
577
  }
481
578
  return results;
482
- };
579
+ }
483
580
 
484
- Controller.prototype.lookUp = function(e) {
581
+ // Searching!
582
+ lookUp(e) {
485
583
  var query, wait;
486
584
  if (e && e.type === 'click' && !this.getOpt('lookUpOnClick')) {
487
585
  return;
@@ -501,9 +599,9 @@ Controller = (function() {
501
599
  this._lookUp(query);
502
600
  }
503
601
  return query;
504
- };
602
+ }
505
603
 
506
- Controller.prototype._delayLookUp = function(query, wait) {
604
+ _delayLookUp(query, wait) {
507
605
  var now, remaining;
508
606
  now = Date.now ? Date.now() : new Date().getTime();
509
607
  this.previousCallTime || (this.previousCallTime = now);
@@ -511,13 +609,11 @@ Controller = (function() {
511
609
  if ((0 < remaining && remaining < wait)) {
512
610
  this.previousCallTime = now;
513
611
  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);
612
+ return this.delayedCallTimeout = setTimeout(() => {
613
+ this.previousCallTime = 0;
614
+ this.delayedCallTimeout = null;
615
+ return this._lookUp(query);
616
+ }, wait);
521
617
  } else {
522
618
  this._stopDelayedCall();
523
619
  if (this.previousCallTime !== now) {
@@ -525,22 +621,23 @@ Controller = (function() {
525
621
  }
526
622
  return this._lookUp(query);
527
623
  }
528
- };
624
+ }
529
625
 
530
- Controller.prototype._stopDelayedCall = function() {
626
+ _stopDelayedCall() {
531
627
  if (this.delayedCallTimeout) {
532
628
  clearTimeout(this.delayedCallTimeout);
533
629
  return this.delayedCallTimeout = null;
534
630
  }
535
- };
631
+ }
536
632
 
537
- Controller.prototype._generateQueryCBId = function() {
633
+ _generateQueryCBId() {
538
634
  return {};
539
- };
635
+ }
540
636
 
541
- Controller.prototype._lookUp = function(query) {
637
+ _lookUp(query) {
542
638
  var _callback;
543
639
  _callback = function(queryCBId, data) {
640
+ // ensure only the latest instance of this function perform actions
544
641
  if (queryCBId !== this.expectedQueryCBId) {
545
642
  return;
546
643
  }
@@ -552,24 +649,17 @@ Controller = (function() {
552
649
  };
553
650
  this.expectedQueryCBId = this._generateQueryCBId();
554
651
  return this.model.query(query.text, $.proxy(_callback, this, this.expectedQueryCBId));
555
- };
556
-
557
- return Controller;
652
+ }
558
653
 
559
- })();
654
+ };
560
655
 
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;
656
+ var TextareaController;
564
657
 
565
- TextareaController = (function(superClass) {
566
- extend(TextareaController, superClass);
658
+ TextareaController = class TextareaController extends Controller {
659
+ // Catch query string behind the at char
567
660
 
568
- function TextareaController() {
569
- return TextareaController.__super__.constructor.apply(this, arguments);
570
- }
571
-
572
- TextareaController.prototype.catchQuery = function() {
661
+ // @return [Hash] Info of the query. Look likes this: {'text': "hello", 'headPos': 0, 'endPos': 0}
662
+ catchQuery() {
573
663
  var caretPos, content, end, isString, query, start, subtext;
574
664
  content = this.$inputor.val();
575
665
  caretPos = this.$inputor.caret('pos', {
@@ -596,9 +686,12 @@ TextareaController = (function(superClass) {
596
686
  this.view.hide();
597
687
  }
598
688
  return this.query = query;
599
- };
689
+ }
600
690
 
601
- TextareaController.prototype.rect = function() {
691
+ // Get offset of current at char(`flag`)
692
+
693
+ // @return [Hash] the offset which look likes this: {top: y, left: x, bottom: bottom}
694
+ rect() {
602
695
  var c, iframeOffset, scaleBottom;
603
696
  if (!(c = this.$inputor.caret('offset', this.pos - 1, {
604
697
  iframe: this.app.iframe
@@ -616,16 +709,19 @@ TextareaController = (function(superClass) {
616
709
  top: c.top,
617
710
  bottom: c.top + c.height + scaleBottom
618
711
  };
619
- };
712
+ }
620
713
 
621
- TextareaController.prototype.insert = function(content, $li) {
714
+ // Insert value of `data-value` attribute of chosen item into inputor
715
+
716
+ // @param content [String] string to insert
717
+ insert(content, $li) {
622
718
  var $inputor, source, startStr, suffix, text;
623
719
  $inputor = this.$inputor;
624
720
  source = $inputor.val();
625
721
  startStr = source.slice(0, Math.max(this.query.headPos - this.at.length, 0));
626
722
  suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || " ";
627
723
  content += suffix;
628
- text = "" + startStr + content + (source.slice(this.query['endPos'] || 0));
724
+ text = `${startStr}${content}${source.slice(this.query['endPos'] || 0)}`;
629
725
  $inputor.val(text);
630
726
  $inputor.caret('pos', startStr.length + content.length, {
631
727
  iframe: this.app.iframe
@@ -634,35 +730,22 @@ TextareaController = (function(superClass) {
634
730
  $inputor.focus();
635
731
  }
636
732
  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;
733
+ }
646
734
 
647
- EditableController = (function(superClass) {
648
- extend(EditableController, superClass);
735
+ };
649
736
 
650
- function EditableController() {
651
- return EditableController.__super__.constructor.apply(this, arguments);
652
- }
737
+ var EditableController;
653
738
 
654
- EditableController.prototype._getRange = function() {
739
+ EditableController = class EditableController extends Controller {
740
+ _getRange() {
655
741
  var sel;
656
742
  sel = this.app.window.getSelection();
657
743
  if (sel.rangeCount > 0) {
658
744
  return sel.getRangeAt(0);
659
745
  }
660
- };
746
+ }
661
747
 
662
- EditableController.prototype._setRange = function(position, node, range) {
663
- if (range == null) {
664
- range = this._getRange();
665
- }
748
+ _setRange(position, node, range = this._getRange()) {
666
749
  if (!(range && node)) {
667
750
  return;
668
751
  }
@@ -676,26 +759,23 @@ EditableController = (function(superClass) {
676
759
  }
677
760
  range.collapse(false);
678
761
  return this._clearRange(range);
679
- };
762
+ }
680
763
 
681
- EditableController.prototype._clearRange = function(range) {
764
+ _clearRange(range = this._getRange()) {
682
765
  var sel;
683
- if (range == null) {
684
- range = this._getRange();
685
- }
686
766
  sel = this.app.window.getSelection();
687
767
  if (this.ctrl_a_pressed == null) {
688
768
  sel.removeAllRanges();
689
769
  return sel.addRange(range);
690
770
  }
691
- };
771
+ }
692
772
 
693
- EditableController.prototype._movingEvent = function(e) {
773
+ _movingEvent(e) {
694
774
  var ref;
695
775
  return e.type === 'click' || ((ref = e.which) === KEY_CODE.RIGHT || ref === KEY_CODE.LEFT || ref === KEY_CODE.UP || ref === KEY_CODE.DOWN);
696
- };
776
+ }
697
777
 
698
- EditableController.prototype._unwrap = function(node) {
778
+ _unwrap(node) {
699
779
  var next;
700
780
  node = $(node).unwrap().get(0);
701
781
  if ((next = node.nextSibling) && next.nodeValue) {
@@ -703,9 +783,9 @@ EditableController = (function(superClass) {
703
783
  $(next).remove();
704
784
  }
705
785
  return node;
706
- };
786
+ }
707
787
 
708
- EditableController.prototype.catchQuery = function(e) {
788
+ catchQuery(e) {
709
789
  var $inserted, $query, _range, index, inserted, isString, lastNode, matched, offset, query, query_content, range;
710
790
  if (!(range = this._getRange())) {
711
791
  return;
@@ -722,6 +802,9 @@ EditableController = (function(superClass) {
722
802
  this._clearRange();
723
803
  return;
724
804
  }
805
+ // absorb range
806
+ // The range at the end of an element is not inside in firefox but not others browsers including IE.
807
+ // To normolize them, we have to move the range inside the element while deleting content or moving caret right after .atwho-inserted
725
808
  if (/firefox/i.test(navigator.userAgent)) {
726
809
  if ($(range.startContainer).is(this.$inputor)) {
727
810
  this._clearRange();
@@ -741,6 +824,7 @@ EditableController = (function(superClass) {
741
824
  }
742
825
  }
743
826
  }
827
+ // modifying inserted element
744
828
  $(range.startContainer).closest('.atwho-inserted').addClass('atwho-query').siblings().removeClass('atwho-query');
745
829
  if (($query = $(".atwho-query", this.app.document)).length > 0 && $query.is(':empty') && $query.text().length === 0) {
746
830
  $query.remove();
@@ -760,6 +844,7 @@ EditableController = (function(superClass) {
760
844
  return;
761
845
  }
762
846
  }
847
+ // matching
763
848
  if ($query.length > 0 && (query_content = $query.attr('data-atwho-at-query'))) {
764
849
  $query.empty().html(query_content).attr('data-atwho-at-query', null);
765
850
  this._setRange('after', $query.get(0), range);
@@ -768,6 +853,7 @@ EditableController = (function(superClass) {
768
853
  _range.setStart(range.startContainer, 0);
769
854
  matched = this.callbacks("matcher").call(this, this.at, _range.toString(), this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
770
855
  isString = typeof matched === 'string';
856
+ // wrapping query with .atwho-query
771
857
  if ($query.length === 0 && isString && (index = range.startOffset - this.at.length - matched.length) >= 0) {
772
858
  range.setStart(range.startContainer, index);
773
859
  $query = $('<span/>', this.app.document).attr(this.getOpt("editableAtwhoQueryAttrs")).addClass('atwho-query');
@@ -786,6 +872,7 @@ EditableController = (function(superClass) {
786
872
  if (isString && matched.length < this.getOpt('minLen', 0)) {
787
873
  return;
788
874
  }
875
+ // handle the matched result
789
876
  if (isString && matched.length <= this.getOpt('maxLen', 20)) {
790
877
  query = {
791
878
  text: matched,
@@ -807,11 +894,16 @@ EditableController = (function(superClass) {
807
894
  }
808
895
  return null;
809
896
  }
810
- };
897
+ }
898
+
899
+ // Get offset of current at char(`flag`)
811
900
 
812
- EditableController.prototype.rect = function() {
901
+ // @return [Hash] the offset which look likes this: {top: y, left: x, bottom: bottom}
902
+ rect() {
813
903
  var $iframe, iframeOffset, rect;
814
904
  rect = this.query.el.offset();
905
+ // do not use {top: 0, left: 0} from jQuery when element is hidden
906
+ // happens every other time the menu is displayed on click in contenteditable
815
907
  if (!(rect && this.query.el[0].getClientRects().length)) {
816
908
  return;
817
909
  }
@@ -822,9 +914,12 @@ EditableController = (function(superClass) {
822
914
  }
823
915
  rect.bottom = rect.top + this.query.el.height();
824
916
  return rect;
825
- };
917
+ }
918
+
919
+ // Insert value of `data-value` attribute of chosen item into inputor
826
920
 
827
- EditableController.prototype.insert = function(content, $li) {
921
+ // @param content [String] string to insert
922
+ insert(content, $li) {
828
923
  var data, overrides, range, suffix, suffixNode;
829
924
  if (!this.$inputor.is(':focus')) {
830
925
  this.$inputor.focus();
@@ -848,30 +943,35 @@ EditableController = (function(superClass) {
848
943
  this.$inputor.focus();
849
944
  }
850
945
  return this.$inputor.change();
851
- };
852
-
853
- return EditableController;
946
+ }
854
947
 
855
- })(Controller);
948
+ };
856
949
 
950
+ // Class to process data
857
951
  var Model;
858
952
 
859
- Model = (function() {
860
- function Model(context) {
953
+ Model = class Model {
954
+ constructor(context) {
861
955
  this.context = context;
862
956
  this.at = this.context.at;
957
+ // NOTE: bind data storage to inputor maybe App class can handle it.
863
958
  this.storage = this.context.$inputor;
864
959
  }
865
960
 
866
- Model.prototype.destroy = function() {
961
+ destroy() {
867
962
  return this.storage.data(this.at, null);
868
- };
963
+ }
869
964
 
870
- Model.prototype.saved = function() {
965
+ saved() {
871
966
  return this.fetch() > 0;
872
- };
967
+ }
873
968
 
874
- Model.prototype.query = function(query, callback) {
969
+ // fetch data from storage by query.
970
+ // will invoke `callback` to return data
971
+
972
+ // @param query [String] catched string for searching
973
+ // @param callback [Function] for receiving data
974
+ query(query, callback) {
875
975
  var _remoteFilter, data, searchKey;
876
976
  data = this.fetch();
877
977
  searchKey = this.context.getOpt("searchKey");
@@ -882,51 +982,66 @@ Model = (function() {
882
982
  } else {
883
983
  return _remoteFilter.call(this.context, query, callback);
884
984
  }
885
- };
985
+ }
886
986
 
887
- Model.prototype.fetch = function() {
987
+ // get or set current data which would be shown on the list view.
988
+
989
+ // @param data [Array] set data
990
+ // @return [Array|undefined] current data that are showing on the list view.
991
+ fetch() {
888
992
  return this.storage.data(this.at) || [];
889
- };
993
+ }
890
994
 
891
- Model.prototype.save = function(data) {
995
+ // save special flag's data to storage
996
+
997
+ // @param data [Array] data to save
998
+ save(data) {
892
999
  return this.storage.data(this.at, this.context.callbacks("beforeSave").call(this.context, data || []));
893
- };
1000
+ }
894
1001
 
895
- Model.prototype.load = function(data) {
1002
+ // load data. It wouldn't load for a second time if it has been loaded.
1003
+
1004
+ // @param data [Array] data to load
1005
+ load(data) {
896
1006
  if (!(this.saved() || !data)) {
897
1007
  return this._load(data);
898
1008
  }
899
- };
1009
+ }
900
1010
 
901
- Model.prototype.reload = function(data) {
1011
+ reload(data) {
902
1012
  return this._load(data);
903
- };
1013
+ }
904
1014
 
905
- Model.prototype._load = function(data) {
1015
+ // load data from local or remote with callback
1016
+
1017
+ // @param data [Array|String] data to load.
1018
+ _load(data) {
906
1019
  if (typeof data === "string") {
907
1020
  throw new Error("@gitlab/at.js removed support for $.ajax");
908
1021
  } else {
909
1022
  return this.save(data);
910
1023
  }
911
- };
912
-
913
- return Model;
1024
+ }
914
1025
 
915
- })();
1026
+ };
916
1027
 
1028
+ // View class to control how At.js's view showing.
1029
+ // All classes share the same DOM view.
917
1030
  var View;
918
1031
 
919
- View = (function() {
920
- function View(context) {
1032
+ View = class View {
1033
+ // @param controller [Object] The Controller.
1034
+ constructor(context) {
921
1035
  this.context = context;
922
1036
  this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>");
923
1037
  this.$elUl = this.$el.children();
924
1038
  this.timeoutID = null;
1039
+ // create HTML DOM of list view if it does not exist
925
1040
  this.context.$el.append(this.$el);
926
1041
  this.bindEvent();
927
1042
  }
928
1043
 
929
- View.prototype.init = function() {
1044
+ init() {
930
1045
  var header_tpl, id;
931
1046
  id = this.context.getOpt("alias") || this.context.at.charCodeAt(0);
932
1047
  header_tpl = this.context.getOpt("headerTpl");
@@ -934,53 +1049,53 @@ View = (function() {
934
1049
  this.$el.prepend(header_tpl);
935
1050
  }
936
1051
  return this.$el.attr({
937
- 'id': "at-view-" + id
1052
+ 'id': `at-view-${id}`
938
1053
  });
939
- };
1054
+ }
940
1055
 
941
- View.prototype.destroy = function() {
1056
+ destroy() {
942
1057
  return this.$el.remove();
943
- };
1058
+ }
944
1059
 
945
- View.prototype.bindEvent = function() {
1060
+ bindEvent() {
946
1061
  var $menu, lastCoordX, lastCoordY;
947
1062
  $menu = this.$el.find('ul');
948
1063
  lastCoordX = 0;
949
1064
  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
- };
1065
+ return $menu.on('mousemove.atwho-view', 'li', (e) => {
1066
+ var $cur;
1067
+ // If the mouse hasn't actually moved then exit.
1068
+ if (lastCoordX === e.clientX && lastCoordY === e.clientY) {
1069
+ return;
1070
+ }
1071
+ lastCoordX = e.clientX;
1072
+ lastCoordY = e.clientY;
1073
+ $cur = $(e.currentTarget);
1074
+ if ($cur.hasClass('cur')) {
1075
+ return;
1076
+ }
1077
+ $menu.find('.cur').removeClass('cur');
1078
+ return $cur.addClass('cur');
1079
+ }).on('click.atwho-view', 'li', (e) => {
1080
+ $menu.find('.cur').removeClass('cur');
1081
+ $(e.currentTarget).addClass('cur');
1082
+ this.choose(e);
1083
+ return e.preventDefault();
1084
+ });
1085
+ }
974
1086
 
975
- View.prototype.visible = function() {
1087
+ // Check if view is visible
1088
+
1089
+ // @return [Boolean]
1090
+ visible() {
976
1091
  return $.expr.filters.visible(this.$el[0]);
977
- };
1092
+ }
978
1093
 
979
- View.prototype.highlighted = function() {
1094
+ highlighted() {
980
1095
  return this.$el.find(".cur").length > 0;
981
- };
1096
+ }
982
1097
 
983
- View.prototype.choose = function(e) {
1098
+ choose(e) {
984
1099
  var $li, content;
985
1100
  if (($li = this.$el.find(".cur")).length) {
986
1101
  content = this.context.insertContentFor($li);
@@ -992,9 +1107,9 @@ View = (function() {
992
1107
  if (this.context.getOpt("hideWithoutSuffix")) {
993
1108
  return this.stopShowing = true;
994
1109
  }
995
- };
1110
+ }
996
1111
 
997
- View.prototype.reposition = function(rect) {
1112
+ reposition(rect) {
998
1113
  var _window, offset, overflowOffset, ref;
999
1114
  _window = this.context.app.iframeAsRoot ? this.context.app.window : window;
1000
1115
  if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {
@@ -1012,9 +1127,9 @@ View = (function() {
1012
1127
  }
1013
1128
  this.$el.offset(offset);
1014
1129
  return this.context.trigger("reposition", [offset]);
1015
- };
1130
+ }
1016
1131
 
1017
- View.prototype.next = function() {
1132
+ next() {
1018
1133
  var cur, next, nextEl, offset;
1019
1134
  cur = this.$el.find('.cur').removeClass('cur');
1020
1135
  next = cur.next();
@@ -1025,9 +1140,9 @@ View = (function() {
1025
1140
  nextEl = next[0];
1026
1141
  offset = nextEl.offsetTop + nextEl.offsetHeight + (nextEl.nextSibling ? nextEl.nextSibling.offsetHeight : 0);
1027
1142
  return this.scrollTop(Math.max(0, offset - this.$el.height()));
1028
- };
1143
+ }
1029
1144
 
1030
- View.prototype.prev = function() {
1145
+ prev() {
1031
1146
  var cur, offset, prev, prevEl;
1032
1147
  cur = this.$el.find('.cur').removeClass('cur');
1033
1148
  prev = cur.prev();
@@ -1038,13 +1153,13 @@ View = (function() {
1038
1153
  prevEl = prev[0];
1039
1154
  offset = prevEl.offsetTop + prevEl.offsetHeight + (prevEl.nextSibling ? prevEl.nextSibling.offsetHeight : 0);
1040
1155
  return this.scrollTop(Math.max(0, offset - this.$el.height()));
1041
- };
1156
+ }
1042
1157
 
1043
- View.prototype.scrollTop = function(scrollTop) {
1158
+ scrollTop(scrollTop) {
1044
1159
  return this.$elUl.scrollTop(scrollTop);
1045
- };
1160
+ }
1046
1161
 
1047
- View.prototype.show = function() {
1162
+ show() {
1048
1163
  var rect;
1049
1164
  if (this.stopShowing) {
1050
1165
  this.stopShowing = false;
@@ -1058,9 +1173,9 @@ View = (function() {
1058
1173
  if (rect = this.context.rect()) {
1059
1174
  return this.reposition(rect);
1060
1175
  }
1061
- };
1176
+ }
1062
1177
 
1063
- View.prototype.hide = function(e, time) {
1178
+ hide(e, time) {
1064
1179
  var callback;
1065
1180
  if (!this.visible()) {
1066
1181
  return;
@@ -1069,17 +1184,16 @@ View = (function() {
1069
1184
  this.$el.hide();
1070
1185
  return this.context.trigger('hidden', [e]);
1071
1186
  } else {
1072
- callback = (function(_this) {
1073
- return function() {
1074
- return _this.hide();
1075
- };
1076
- })(this);
1187
+ callback = () => {
1188
+ return this.hide();
1189
+ };
1077
1190
  clearTimeout(this.timeoutID);
1078
1191
  return this.timeoutID = setTimeout(callback, time);
1079
1192
  }
1080
- };
1193
+ }
1081
1194
 
1082
- View.prototype.render = function(list) {
1195
+ // render list view
1196
+ render(list) {
1083
1197
  var $li, $ul, i, item, len, li, tpl;
1084
1198
  if (!($.isArray(list) && list.length > 0)) {
1085
1199
  this.hide();
@@ -1102,15 +1216,17 @@ View = (function() {
1102
1216
  if (this.context.getOpt('highlightFirst')) {
1103
1217
  return $ul.find("li:first").addClass("cur");
1104
1218
  }
1105
- };
1106
-
1107
- return View;
1219
+ }
1108
1220
 
1109
- })();
1221
+ };
1110
1222
 
1111
1223
  var Api;
1112
1224
 
1113
1225
  Api = {
1226
+ // load a flag's data
1227
+
1228
+ // @params at[String] the flag
1229
+ // @params data [Array] data to storage.
1114
1230
  load: function(at, data) {
1115
1231
  var c;
1116
1232
  if (c = this.controller(at)) {
@@ -1158,7 +1274,7 @@ $.fn.atwho = function(method) {
1158
1274
  } else if (Api[method] && app) {
1159
1275
  return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
1160
1276
  } else {
1161
- return $.error("Method " + method + " does not exist on jQuery.atwho");
1277
+ return $.error(`Method ${method} does not exist on jQuery.atwho`);
1162
1278
  }
1163
1279
  });
1164
1280
  if (result != null) {
@@ -1168,7 +1284,7 @@ $.fn.atwho = function(method) {
1168
1284
  }
1169
1285
  };
1170
1286
 
1171
- $.fn.atwho["default"] = {
1287
+ $.fn.atwho.default = {
1172
1288
  at: void 0,
1173
1289
  alias: void 0,
1174
1290
  data: null,