4 |
5 | > Permission is hereby granted, free of charge, to any person obtaining a copy
6 | > of this software and associated documentation files (the "Software"), to deal
7 | > in the Software without restriction, including without limitation the rights
8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | > copies of the Software, and to permit persons to whom the Software is
10 | > furnished to do so, subject to the following conditions:
11 | >
12 | > The above copyright notice and this permission notice shall be included in
13 | > all copies or substantial portions of the Software.
14 | >
15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | > THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/:package_name).
6 |
7 |
8 | ## Pull Requests
9 |
10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
11 |
12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
13 |
14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
15 |
16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
17 |
18 | - **Create feature branches** - Don't ask us to pull from your master branch.
19 |
20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
21 |
22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
23 |
24 |
25 | ## Running Tests
26 |
27 | ``` bash
28 | $ composer test
29 | ```
30 |
31 |
32 | **Happy coding**!
33 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unicodeveloper/laravel-mentions",
3 | "description": "Laravel 5 package that provides facebook-like mention functionality",
4 | "keywords": [
5 | "laravel",
6 | "laravel 5",
7 | "laravel-mentions",
8 | "facebook-mentions",
9 | "autocomplete",
10 | "smart search"
11 | ],
12 | "homepage": "https://github.com/unicodeveloper/laravel-mentions",
13 | "license": "MIT",
14 | "authors": [
15 | {
16 | "name": "Prosper Otemuyiwa",
17 | "email": "prosperotemuyiwa@gmail.com",
18 | "homepage": "https://twitter.com/unicodeveloper",
19 | "role": "Developer"
20 | }
21 | ],
22 | "require": {
23 | "php" : ">=5.5.9",
24 | "laravelcollective/html": "~5.0|~5.1"
25 | },
26 | "require-dev": {
27 | "phpunit/phpunit" : "4.*",
28 | "scrutinizer/ocular": "~1.1"
29 | },
30 | "autoload": {
31 | "psr-4": {
32 | "Unicodeveloper\\Mention\\": "src"
33 | },
34 | "files": [
35 | "src/helper.php"
36 | ]
37 | },
38 | "autoload-dev": {
39 | "psr-4": {
40 | "Unicodeveloper\\Mention\\Test\\": "tests"
41 | }
42 | },
43 | "scripts": {
44 | "test": "phpunit"
45 | },
46 | "extra": {
47 | "branch-alias": {
48 | "dev-master": "1.0-dev"
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/MentionServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes([
29 | $config => config_path('mentions.php'),
30 | $views => base_path('resources/views/vendor/mentions'),
31 | $script => public_path('js'),
32 | ]);
33 |
34 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'mentions');
35 |
36 | if (! $this->app->routesAreCached()) {
37 | require __DIR__.'/Http/routes.php';
38 | }
39 | }
40 |
41 |
42 | /**
43 | * Register the application services
44 | * @return void
45 | */
46 | public function register()
47 | {
48 | $this->app->singleton('mentionBuilder', function ($app) {
49 | $form = new MentionBuilder($app['html'], $app['url'], $app['session.store']->getToken());
50 |
51 | return $form->setSessionStore($app['session.store']);
52 | });
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/Factory/MentionBuilder.php:
--------------------------------------------------------------------------------
1 | text($name, $value, [
40 | 'id' => 'mention-' . $name,
41 | 'class' => $class
42 | ]);
43 |
44 |
45 | $scriptTag = <<< EOT
46 |
51 | EOT;
52 |
53 | return $scriptTag.$input;
54 | }
55 |
56 | /**
57 | * Create a textarea input field.
58 | *
59 | * @param string $name
60 | * @param string $value
61 | * @param string $type
62 | * @param string $column
63 | * @param string $class
64 | *
65 | * @return string
66 | */
67 | public function asTextArea($name, $value, $type, $column, $class = '')
68 | {
69 | $input = $this->textarea($name, $value, [
70 | 'id' => 'mention-' . $name,
71 | 'class' => $class
72 | ]);
73 |
74 | $scriptTag = <<< EOT
75 |
80 | EOT;
81 |
82 | return $scriptTag.$input;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # laravel-mentions
2 |
3 | [](https://packagist.org/packages/unicodeveloper/laravel-mentions)
4 | 
5 | [](LICENSE.md)
6 | [](https://travis-ci.org/unicodeveloper/laravel-mentions)
7 | [](https://scrutinizer-ci.com/g/unicodeveloper/laravel-mentions)
8 | [](https://packagist.org/packages/unicodeveloper/laravel-mentions)
9 |
10 | This package makes it possible to create text/textarea fields that enable **mentioning** by using [At.js](https://github.com/ichord/At.js).
11 |
12 | The data for the autocomplete is loaded from a route which will load data based on predefined key-value pairs of an alias and a model in the config.
13 |
14 | ## Installation
15 |
16 | First, pull in the package through Composer.
17 |
18 | ```js
19 | "require": {
20 | "unicodeveloper/laravel-mentions": "1.1.*"
21 | }
22 | ```
23 |
24 | And then include these service providers within `config/app.php`.
25 |
26 | ```php
27 | 'providers' => [
28 | Unicodeveloper\Mention\MentionServiceProvider::class,
29 | Collective\Html\HtmlServiceProvider::class,
30 | ];
31 | ```
32 |
33 | If you need to modify the configuration or the views, you can run:
34 |
35 | ```bash
36 | php artisan vendor:publish
37 | ```
38 |
39 | The package views will now be located in the `app/resources/views/vendor/mentions/` directory and the configuration will be located at `config/mentions.php`.
40 |
41 | ## Configuration
42 |
43 | To make it possible for At.js to load data we need to define key-value pairs that consist of an alias and a corresponding model.
44 |
45 | ```php
46 | return [
47 |
48 | 'users' => 'App\User', // responds to /api/mentions/users
49 | 'friends' => 'App\Friend', // responds to /api/mentions/friends
50 | 'clients' => 'App\Client', // responds to /api/mentions/clients
51 | 'supports' => 'App\Supporter', // responds to /api/mentions/supports
52 |
53 | ];
54 | ```
55 |
56 | So now with these `aliases` configured we could create a new textfield which will send a request to the `users` route and search for matching data in the `name` column.
57 |
58 | ```php
59 | {!! mention()->asText('recipient', old('recipient'), 'users', 'name') !!}
60 | ```
61 |
62 | You can also add a class name for styling of the text and textareas, that's the last argument. In this example, it is `user-form`
63 |
64 | ```php
65 | {!! mention()->asText('recipient', old('recipient'), 'users', 'name', 'user-form') !!}
66 | ```
67 |
68 | ## Example
69 |
70 | ```html
71 |
72 |
73 |
74 |
75 | Laravel PHP Framework
76 |
77 |
78 |
79 |
80 |
81 |
82 | @include('mentions::assets')
83 |
84 |
85 |
86 |
87 | {!! mention()->asText('recipient', old('recipient'), 'users', 'name') !!}
88 | {!! mention()->asTextArea('message', old('message'), 'users', 'name') !!}
89 |
90 |
91 |
92 | ```
93 |
94 |
95 | ## Install
96 |
97 | Via Composer
98 |
99 | ``` bash
100 | $ composer require unicodeveloper/laravel-mentions
101 | ```
102 |
103 | ## Change log
104 |
105 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
106 |
107 | ## Testing
108 |
109 | ``` bash
110 | $ composer test
111 | ```
112 |
113 | ## Contributing
114 |
115 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
116 |
117 | ## How can I thank you?
118 |
119 | Why not star the github repo? I'd love the attention! Why not share the link for this repository on Twitter or HackerNews? Spread the word!
120 |
121 | Don't forget to [follow me on twitter](https://twitter.com/unicodeveloper)!
122 |
123 | Thanks!
124 | Prosper Otemuyiwa.
125 |
126 | ## License
127 |
128 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
129 |
130 |
--------------------------------------------------------------------------------
/dist/jquery.caret.js:
--------------------------------------------------------------------------------
1 | (function (root, factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | // AMD. Register as an anonymous module.
4 | define(["jquery"], function ($) {
5 | return (root.returnExportsGlobal = factory($));
6 | });
7 | } else if (typeof exports === 'object') {
8 | // Node. Does not work with strict CommonJS, but
9 | // only CommonJS-like enviroments that support module.exports,
10 | // like Node.
11 | module.exports = factory(require("jquery"));
12 | } else {
13 | factory(jQuery);
14 | }
15 | }(this, function ($) {
16 |
17 | /*
18 | Implement Github like autocomplete mentions
19 | http://ichord.github.com/At.js
20 |
21 | Copyright (c) 2013 chord.luo@gmail.com
22 | Licensed under the MIT license.
23 | */
24 |
25 | /*
26 | 本插件操作 textarea 或者 input 内的插入符
27 | 只实现了获得插入符在文本框中的位置,我设置
28 | 插入符的位置.
29 | */
30 |
31 | "use strict";
32 | var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy;
33 |
34 | pluginName = 'caret';
35 |
36 | EditableCaret = (function() {
37 | function EditableCaret($inputor) {
38 | this.$inputor = $inputor;
39 | this.domInputor = this.$inputor[0];
40 | }
41 |
42 | EditableCaret.prototype.setPos = function(pos) {
43 | return this.domInputor;
44 | };
45 |
46 | EditableCaret.prototype.getIEPosition = function() {
47 | return this.getPosition();
48 | };
49 |
50 | EditableCaret.prototype.getPosition = function() {
51 | var inputor_offset, offset;
52 | offset = this.getOffset();
53 | inputor_offset = this.$inputor.offset();
54 | offset.left -= inputor_offset.left;
55 | offset.top -= inputor_offset.top;
56 | return offset;
57 | };
58 |
59 | EditableCaret.prototype.getOldIEPos = function() {
60 | var preCaretTextRange, textRange;
61 | textRange = oDocument.selection.createRange();
62 | preCaretTextRange = oDocument.body.createTextRange();
63 | preCaretTextRange.moveToElementText(this.domInputor);
64 | preCaretTextRange.setEndPoint("EndToEnd", textRange);
65 | return preCaretTextRange.text.length;
66 | };
67 |
68 | EditableCaret.prototype.getPos = function() {
69 | var clonedRange, pos, range;
70 | if (range = this.range()) {
71 | clonedRange = range.cloneRange();
72 | clonedRange.selectNodeContents(this.domInputor);
73 | clonedRange.setEnd(range.endContainer, range.endOffset);
74 | pos = clonedRange.toString().length;
75 | clonedRange.detach();
76 | return pos;
77 | } else if (oDocument.selection) {
78 | return this.getOldIEPos();
79 | }
80 | };
81 |
82 | EditableCaret.prototype.getOldIEOffset = function() {
83 | var range, rect;
84 | range = oDocument.selection.createRange().duplicate();
85 | range.moveStart("character", -1);
86 | rect = range.getBoundingClientRect();
87 | return {
88 | height: rect.bottom - rect.top,
89 | left: rect.left,
90 | top: rect.top
91 | };
92 | };
93 |
94 | EditableCaret.prototype.getOffset = function(pos) {
95 | var clonedRange, offset, range, rect, shadowCaret;
96 | if (oWindow.getSelection && (range = this.range())) {
97 | if (range.endOffset - 1 > 0 && range.endContainer === !this.domInputor) {
98 | clonedRange = range.cloneRange();
99 | clonedRange.setStart(range.endContainer, range.endOffset - 1);
100 | clonedRange.setEnd(range.endContainer, range.endOffset);
101 | rect = clonedRange.getBoundingClientRect();
102 | offset = {
103 | height: rect.height,
104 | left: rect.left + rect.width,
105 | top: rect.top
106 | };
107 | clonedRange.detach();
108 | }
109 | if (!offset || (offset != null ? offset.height : void 0) === 0) {
110 | clonedRange = range.cloneRange();
111 | shadowCaret = $(oDocument.createTextNode("|"));
112 | clonedRange.insertNode(shadowCaret[0]);
113 | clonedRange.selectNode(shadowCaret[0]);
114 | rect = clonedRange.getBoundingClientRect();
115 | offset = {
116 | height: rect.height,
117 | left: rect.left,
118 | top: rect.top
119 | };
120 | shadowCaret.remove();
121 | clonedRange.detach();
122 | }
123 | } else if (oDocument.selection) {
124 | offset = this.getOldIEOffset();
125 | }
126 | if (offset) {
127 | offset.top += $(oWindow).scrollTop();
128 | offset.left += $(oWindow).scrollLeft();
129 | }
130 | return offset;
131 | };
132 |
133 | EditableCaret.prototype.range = function() {
134 | var sel;
135 | if (!oWindow.getSelection) {
136 | return;
137 | }
138 | sel = oWindow.getSelection();
139 | if (sel.rangeCount > 0) {
140 | return sel.getRangeAt(0);
141 | } else {
142 | return null;
143 | }
144 | };
145 |
146 | return EditableCaret;
147 |
148 | })();
149 |
150 | InputCaret = (function() {
151 | function InputCaret($inputor) {
152 | this.$inputor = $inputor;
153 | this.domInputor = this.$inputor[0];
154 | }
155 |
156 | InputCaret.prototype.getIEPos = function() {
157 | var endRange, inputor, len, normalizedValue, pos, range, textInputRange;
158 | inputor = this.domInputor;
159 | range = oDocument.selection.createRange();
160 | pos = 0;
161 | if (range && range.parentElement() === inputor) {
162 | normalizedValue = inputor.value.replace(/\r\n/g, "\n");
163 | len = normalizedValue.length;
164 | textInputRange = inputor.createTextRange();
165 | textInputRange.moveToBookmark(range.getBookmark());
166 | endRange = inputor.createTextRange();
167 | endRange.collapse(false);
168 | if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
169 | pos = len;
170 | } else {
171 | pos = -textInputRange.moveStart("character", -len);
172 | }
173 | }
174 | return pos;
175 | };
176 |
177 | InputCaret.prototype.getPos = function() {
178 | if (oDocument.selection) {
179 | return this.getIEPos();
180 | } else {
181 | return this.domInputor.selectionStart;
182 | }
183 | };
184 |
185 | InputCaret.prototype.setPos = function(pos) {
186 | var inputor, range;
187 | inputor = this.domInputor;
188 | if (oDocument.selection) {
189 | range = inputor.createTextRange();
190 | range.move("character", pos);
191 | range.select();
192 | } else if (inputor.setSelectionRange) {
193 | inputor.setSelectionRange(pos, pos);
194 | }
195 | return inputor;
196 | };
197 |
198 | InputCaret.prototype.getIEOffset = function(pos) {
199 | var h, textRange, x, y;
200 | textRange = this.domInputor.createTextRange();
201 | pos || (pos = this.getPos());
202 | textRange.move('character', pos);
203 | x = textRange.boundingLeft;
204 | y = textRange.boundingTop;
205 | h = textRange.boundingHeight;
206 | return {
207 | left: x,
208 | top: y,
209 | height: h
210 | };
211 | };
212 |
213 | InputCaret.prototype.getOffset = function(pos) {
214 | var $inputor, offset, position;
215 | $inputor = this.$inputor;
216 | if (oDocument.selection) {
217 | offset = this.getIEOffset(pos);
218 | offset.top += $(oWindow).scrollTop() + $inputor.scrollTop();
219 | offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft();
220 | return offset;
221 | } else {
222 | offset = $inputor.offset();
223 | position = this.getPosition(pos);
224 | return offset = {
225 | left: offset.left + position.left - $inputor.scrollLeft(),
226 | top: offset.top + position.top - $inputor.scrollTop(),
227 | height: position.height
228 | };
229 | }
230 | };
231 |
232 | InputCaret.prototype.getPosition = function(pos) {
233 | var $inputor, at_rect, end_range, format, html, mirror, start_range;
234 | $inputor = this.$inputor;
235 | format = function(value) {
236 | value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, "
");
237 | if (/firefox/i.test(navigator.userAgent)) {
238 | value = value.replace(/\s/g, ' ');
239 | }
240 | return value;
241 | };
242 | if (pos === void 0) {
243 | pos = this.getPos();
244 | }
245 | start_range = $inputor.val().slice(0, pos);
246 | end_range = $inputor.val().slice(pos);
247 | html = "" + format(start_range) + "";
248 | html += "|";
249 | html += "" + format(end_range) + "";
250 | mirror = new Mirror($inputor);
251 | return at_rect = mirror.create(html).rect();
252 | };
253 |
254 | InputCaret.prototype.getIEPosition = function(pos) {
255 | var h, inputorOffset, offset, x, y;
256 | offset = this.getIEOffset(pos);
257 | inputorOffset = this.$inputor.offset();
258 | x = offset.left - inputorOffset.left;
259 | y = offset.top - inputorOffset.top;
260 | h = offset.height;
261 | return {
262 | left: x,
263 | top: y,
264 | height: h
265 | };
266 | };
267 |
268 | return InputCaret;
269 |
270 | })();
271 |
272 | Mirror = (function() {
273 | Mirror.prototype.css_attr = ["borderBottomWidth", "borderLeftWidth", "borderRightWidth", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "borderTopWidth", "boxSizing", "fontFamily", "fontSize", "fontWeight", "height", "letterSpacing", "lineHeight", "marginBottom", "marginLeft", "marginRight", "marginTop", "outlineWidth", "overflow", "overflowX", "overflowY", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "textAlign", "textOverflow", "textTransform", "whiteSpace", "wordBreak", "wordWrap"];
274 |
275 | function Mirror($inputor) {
276 | this.$inputor = $inputor;
277 | }
278 |
279 | Mirror.prototype.mirrorCss = function() {
280 | var css,
281 | _this = this;
282 | css = {
283 | position: 'absolute',
284 | left: -9999,
285 | top: 0,
286 | zIndex: -20000
287 | };
288 | if (this.$inputor.prop('tagName') === 'TEXTAREA') {
289 | this.css_attr.push('width');
290 | }
291 | $.each(this.css_attr, function(i, p) {
292 | return css[p] = _this.$inputor.css(p);
293 | });
294 | return css;
295 | };
296 |
297 | Mirror.prototype.create = function(html) {
298 | this.$mirror = $('');
299 | this.$mirror.css(this.mirrorCss());
300 | this.$mirror.html(html);
301 | this.$inputor.after(this.$mirror);
302 | return this;
303 | };
304 |
305 | Mirror.prototype.rect = function() {
306 | var $flag, pos, rect;
307 | $flag = this.$mirror.find("#caret");
308 | pos = $flag.position();
309 | rect = {
310 | left: pos.left,
311 | top: pos.top,
312 | height: $flag.height()
313 | };
314 | this.$mirror.remove();
315 | return rect;
316 | };
317 |
318 | return Mirror;
319 |
320 | })();
321 |
322 | Utils = {
323 | contentEditable: function($inputor) {
324 | return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true');
325 | }
326 | };
327 |
328 | methods = {
329 | pos: function(pos) {
330 | if (pos || pos === 0) {
331 | return this.setPos(pos);
332 | } else {
333 | return this.getPos();
334 | }
335 | },
336 | position: function(pos) {
337 | if (oDocument.selection) {
338 | return this.getIEPosition(pos);
339 | } else {
340 | return this.getPosition(pos);
341 | }
342 | },
343 | offset: function(pos) {
344 | var offset;
345 | offset = this.getOffset(pos);
346 | return offset;
347 | }
348 | };
349 |
350 | oDocument = null;
351 |
352 | oWindow = null;
353 |
354 | oFrame = null;
355 |
356 | setContextBy = function(settings) {
357 | var iframe;
358 | if (iframe = settings != null ? settings.iframe : void 0) {
359 | oFrame = iframe;
360 | oWindow = iframe.contentWindow;
361 | return oDocument = iframe.contentDocument || oWindow.document;
362 | } else {
363 | oFrame = void 0;
364 | oWindow = window;
365 | return oDocument = document;
366 | }
367 | };
368 |
369 | discoveryIframeOf = function($dom) {
370 | var error;
371 | oDocument = $dom[0].ownerDocument;
372 | oWindow = oDocument.defaultView || oDocument.parentWindow;
373 | try {
374 | return oFrame = oWindow.frameElement;
375 | } catch (_error) {
376 | error = _error;
377 | }
378 | };
379 |
380 | $.fn.caret = function(method, value, settings) {
381 | var caret;
382 | if (methods[method]) {
383 | if ($.isPlainObject(value)) {
384 | setContextBy(value);
385 | value = void 0;
386 | } else {
387 | setContextBy(settings);
388 | }
389 | caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this);
390 | return methods[method].apply(caret, [value]);
391 | } else {
392 | return $.error("Method " + method + " does not exist on jQuery.caret");
393 | }
394 | };
395 |
396 | $.fn.caret.EditableCaret = EditableCaret;
397 |
398 | $.fn.caret.InputCaret = InputCaret;
399 |
400 | $.fn.caret.Utils = Utils;
401 |
402 | $.fn.caret.apis = methods;
403 |
404 |
405 | }));
406 |
--------------------------------------------------------------------------------
/dist/jquery.atwho.js:
--------------------------------------------------------------------------------
1 | /*! jquery.atwho - v1.4.0 %>
2 | * Copyright (c) 2015 chord.luo ;
3 | * homepage: http://ichord.github.com/At.js
4 | * Licensed MIT
5 | */
6 | (function (root, factory) {
7 | if (typeof define === 'function' && define.amd) {
8 | // AMD. Register as an anonymous module unless amdModuleId is set
9 | define(["jquery"], function (a0) {
10 | return (factory(a0));
11 | });
12 | } else if (typeof exports === 'object') {
13 | // Node. Does not work with strict CommonJS, but
14 | // only CommonJS-like environments that support module.exports,
15 | // like Node.
16 | module.exports = factory(require("jquery"));
17 | } else {
18 | factory(jQuery);
19 | }
20 | }(this, function (jquery) {
21 |
22 | var $, Api, App, Controller, DEFAULT_CALLBACKS, EditableController, KEY_CODE, Model, TextareaController, View,
23 | slice = [].slice,
24 | 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; },
25 | hasProp = {}.hasOwnProperty;
26 |
27 | $ = jquery;
28 |
29 | App = (function() {
30 | function App(inputor) {
31 | this.currentFlag = null;
32 | this.controllers = {};
33 | this.aliasMaps = {};
34 | this.$inputor = $(inputor);
35 | this.setupRootElement();
36 | this.listen();
37 | }
38 |
39 | App.prototype.createContainer = function(doc) {
40 | var ref;
41 | if ((ref = this.$el) != null) {
42 | ref.remove();
43 | }
44 | return $(doc.body).append(this.$el = $(""));
45 | };
46 |
47 | App.prototype.setupRootElement = function(iframe, asRoot) {
48 | var error;
49 | if (asRoot == null) {
50 | asRoot = false;
51 | }
52 | if (iframe) {
53 | this.window = iframe.contentWindow;
54 | this.document = iframe.contentDocument || this.window.document;
55 | this.iframe = iframe;
56 | } else {
57 | this.document = this.$inputor[0].ownerDocument;
58 | this.window = this.document.defaultView || this.document.parentWindow;
59 | try {
60 | this.iframe = this.window.frameElement;
61 | } catch (_error) {
62 | error = _error;
63 | this.iframe = null;
64 | if ($.fn.atwho.debug) {
65 | throw new Error("iframe auto-discovery is failed.\nPlease use `setIframe` to set the target iframe manually.\n" + error);
66 | }
67 | }
68 | }
69 | return this.createContainer((this.iframeAsRoot = asRoot) ? this.document : document);
70 | };
71 |
72 | App.prototype.controller = function(at) {
73 | var c, current, currentFlag, ref;
74 | if (this.aliasMaps[at]) {
75 | current = this.controllers[this.aliasMaps[at]];
76 | } else {
77 | ref = this.controllers;
78 | for (currentFlag in ref) {
79 | c = ref[currentFlag];
80 | if (currentFlag === at) {
81 | current = c;
82 | break;
83 | }
84 | }
85 | }
86 | if (current) {
87 | return current;
88 | } else {
89 | return this.controllers[this.currentFlag];
90 | }
91 | };
92 |
93 | App.prototype.setContextFor = function(at) {
94 | this.currentFlag = at;
95 | return this;
96 | };
97 |
98 | App.prototype.reg = function(flag, setting) {
99 | var base, controller;
100 | controller = (base = this.controllers)[flag] || (base[flag] = this.$inputor.is('[contentEditable]') ? new EditableController(this, flag) : new TextareaController(this, flag));
101 | if (setting.alias) {
102 | this.aliasMaps[setting.alias] = flag;
103 | }
104 | controller.init(setting);
105 | return this;
106 | };
107 |
108 | App.prototype.listen = function() {
109 | return this.$inputor.on('compositionstart', (function(_this) {
110 | return function(e) {
111 | var ref;
112 | if ((ref = _this.controller()) != null) {
113 | ref.view.hide();
114 | }
115 | _this.isComposing = true;
116 | return null;
117 | };
118 | })(this)).on('compositionend', (function(_this) {
119 | return function(e) {
120 | _this.isComposing = false;
121 | return null;
122 | };
123 | })(this)).on('keyup.atwhoInner', (function(_this) {
124 | return function(e) {
125 | return _this.onKeyup(e);
126 | };
127 | })(this)).on('keydown.atwhoInner', (function(_this) {
128 | return function(e) {
129 | return _this.onKeydown(e);
130 | };
131 | })(this)).on('blur.atwhoInner', (function(_this) {
132 | return function(e) {
133 | var c;
134 | if (c = _this.controller()) {
135 | c.expectedQueryCBId = null;
136 | return c.view.hide(e, c.getOpt("displayTimeout"));
137 | }
138 | };
139 | })(this)).on('click.atwhoInner', (function(_this) {
140 | return function(e) {
141 | return _this.dispatch(e);
142 | };
143 | })(this)).on('scroll.atwhoInner', (function(_this) {
144 | return function() {
145 | var lastScrollTop;
146 | lastScrollTop = _this.$inputor.scrollTop();
147 | return function(e) {
148 | var currentScrollTop, ref;
149 | currentScrollTop = e.target.scrollTop;
150 | if (lastScrollTop !== currentScrollTop) {
151 | if ((ref = _this.controller()) != null) {
152 | ref.view.hide(e);
153 | }
154 | }
155 | lastScrollTop = currentScrollTop;
156 | return true;
157 | };
158 | };
159 | })(this)());
160 | };
161 |
162 | App.prototype.shutdown = function() {
163 | var _, c, ref;
164 | ref = this.controllers;
165 | for (_ in ref) {
166 | c = ref[_];
167 | c.destroy();
168 | delete this.controllers[_];
169 | }
170 | this.$inputor.off('.atwhoInner');
171 | return this.$el.remove();
172 | };
173 |
174 | App.prototype.dispatch = function(e) {
175 | var _, c, ref, results;
176 | ref = this.controllers;
177 | results = [];
178 | for (_ in ref) {
179 | c = ref[_];
180 | results.push(c.lookUp(e));
181 | }
182 | return results;
183 | };
184 |
185 | App.prototype.onKeyup = function(e) {
186 | var ref;
187 | switch (e.keyCode) {
188 | case KEY_CODE.ESC:
189 | e.preventDefault();
190 | if ((ref = this.controller()) != null) {
191 | ref.view.hide();
192 | }
193 | break;
194 | case KEY_CODE.DOWN:
195 | case KEY_CODE.UP:
196 | case KEY_CODE.CTRL:
197 | case KEY_CODE.ENTER:
198 | $.noop();
199 | break;
200 | case KEY_CODE.P:
201 | case KEY_CODE.N:
202 | if (!e.ctrlKey) {
203 | this.dispatch(e);
204 | }
205 | break;
206 | default:
207 | this.dispatch(e);
208 | }
209 | };
210 |
211 | App.prototype.onKeydown = function(e) {
212 | var ref, view;
213 | view = (ref = this.controller()) != null ? ref.view : void 0;
214 | if (!(view && view.visible())) {
215 | return;
216 | }
217 | switch (e.keyCode) {
218 | case KEY_CODE.ESC:
219 | e.preventDefault();
220 | view.hide(e);
221 | break;
222 | case KEY_CODE.UP:
223 | e.preventDefault();
224 | view.prev();
225 | break;
226 | case KEY_CODE.DOWN:
227 | e.preventDefault();
228 | view.next();
229 | break;
230 | case KEY_CODE.P:
231 | if (!e.ctrlKey) {
232 | return;
233 | }
234 | e.preventDefault();
235 | view.prev();
236 | break;
237 | case KEY_CODE.N:
238 | if (!e.ctrlKey) {
239 | return;
240 | }
241 | e.preventDefault();
242 | view.next();
243 | break;
244 | case KEY_CODE.TAB:
245 | case KEY_CODE.ENTER:
246 | case KEY_CODE.SPACE:
247 | if (!view.visible()) {
248 | return;
249 | }
250 | if (!this.controller().getOpt('spaceSelectsMatch') && e.keyCode === KEY_CODE.SPACE) {
251 | return;
252 | }
253 | if (!this.controller().getOpt('tabSelectsMatch') && e.keyCode === KEY_CODE.TAB) {
254 | return;
255 | }
256 | if (view.highlighted()) {
257 | e.preventDefault();
258 | view.choose(e);
259 | } else {
260 | view.hide(e);
261 | }
262 | break;
263 | default:
264 | $.noop();
265 | }
266 | };
267 |
268 | return App;
269 |
270 | })();
271 |
272 | Controller = (function() {
273 | Controller.prototype.uid = function() {
274 | return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime());
275 | };
276 |
277 | function Controller(app1, at1) {
278 | this.app = app1;
279 | this.at = at1;
280 | this.$inputor = this.app.$inputor;
281 | this.id = this.$inputor[0].id || this.uid();
282 | this.expectedQueryCBId = null;
283 | this.setting = null;
284 | this.query = null;
285 | this.pos = 0;
286 | this.range = null;
287 | if ((this.$el = $("#atwho-ground-" + this.id, this.app.$el)).length === 0) {
288 | this.app.$el.append(this.$el = $(""));
289 | }
290 | this.model = new Model(this);
291 | this.view = new View(this);
292 | }
293 |
294 | Controller.prototype.init = function(setting) {
295 | this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
296 | this.view.init();
297 | return this.model.reload(this.setting.data);
298 | };
299 |
300 | Controller.prototype.destroy = function() {
301 | this.trigger('beforeDestroy');
302 | this.model.destroy();
303 | this.view.destroy();
304 | return this.$el.remove();
305 | };
306 |
307 | Controller.prototype.callDefault = function() {
308 | var args, error, funcName;
309 | funcName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
310 | try {
311 | return DEFAULT_CALLBACKS[funcName].apply(this, args);
312 | } catch (_error) {
313 | error = _error;
314 | return $.error(error + " Or maybe At.js doesn't have function " + funcName);
315 | }
316 | };
317 |
318 | Controller.prototype.trigger = function(name, data) {
319 | var alias, eventName;
320 | if (data == null) {
321 | data = [];
322 | }
323 | data.push(this);
324 | alias = this.getOpt('alias');
325 | eventName = alias ? name + "-" + alias + ".atwho" : name + ".atwho";
326 | return this.$inputor.trigger(eventName, data);
327 | };
328 |
329 | Controller.prototype.callbacks = function(funcName) {
330 | return this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName];
331 | };
332 |
333 | Controller.prototype.getOpt = function(at, default_value) {
334 | var e;
335 | try {
336 | return this.setting[at];
337 | } catch (_error) {
338 | e = _error;
339 | return null;
340 | }
341 | };
342 |
343 | Controller.prototype.insertContentFor = function($li) {
344 | var data, tpl;
345 | tpl = this.getOpt('insertTpl');
346 | data = $.extend({}, $li.data('item-data'), {
347 | 'atwho-at': this.at
348 | });
349 | return this.callbacks("tplEval").call(this, tpl, data, "onInsert");
350 | };
351 |
352 | Controller.prototype.renderView = function(data) {
353 | var searchKey;
354 | searchKey = this.getOpt("searchKey");
355 | data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), searchKey);
356 | return this.view.render(data.slice(0, this.getOpt('limit')));
357 | };
358 |
359 | Controller.arrayToDefaultHash = function(data) {
360 | var i, item, len, results;
361 | if (!$.isArray(data)) {
362 | return data;
363 | }
364 | results = [];
365 | for (i = 0, len = data.length; i < len; i++) {
366 | item = data[i];
367 | if ($.isPlainObject(item)) {
368 | results.push(item);
369 | } else {
370 | results.push({
371 | name: item
372 | });
373 | }
374 | }
375 | return results;
376 | };
377 |
378 | Controller.prototype.lookUp = function(e) {
379 | var query, wait;
380 | if (e && e.type === 'click' && !this.getOpt('lookUpOnClick')) {
381 | return;
382 | }
383 | if (this.getOpt('suspendOnComposing') && this.app.isComposing) {
384 | return;
385 | }
386 | query = this.catchQuery(e);
387 | if (!query) {
388 | this.expectedQueryCBId = null;
389 | return query;
390 | }
391 | this.app.setContextFor(this.at);
392 | if (wait = this.getOpt('delay')) {
393 | this._delayLookUp(query, wait);
394 | } else {
395 | this._lookUp(query);
396 | }
397 | return query;
398 | };
399 |
400 | Controller.prototype._delayLookUp = function(query, wait) {
401 | var now, remaining;
402 | now = Date.now ? Date.now() : new Date().getTime();
403 | this.previousCallTime || (this.previousCallTime = now);
404 | remaining = wait - (now - this.previousCallTime);
405 | if ((0 < remaining && remaining < wait)) {
406 | this.previousCallTime = now;
407 | this._stopDelayedCall();
408 | return this.delayedCallTimeout = setTimeout((function(_this) {
409 | return function() {
410 | _this.previousCallTime = 0;
411 | _this.delayedCallTimeout = null;
412 | return _this._lookUp(query);
413 | };
414 | })(this), wait);
415 | } else {
416 | this._stopDelayedCall();
417 | if (this.previousCallTime !== now) {
418 | this.previousCallTime = 0;
419 | }
420 | return this._lookUp(query);
421 | }
422 | };
423 |
424 | Controller.prototype._stopDelayedCall = function() {
425 | if (this.delayedCallTimeout) {
426 | clearTimeout(this.delayedCallTimeout);
427 | return this.delayedCallTimeout = null;
428 | }
429 | };
430 |
431 | Controller.prototype._generateQueryCBId = function() {
432 | return {};
433 | };
434 |
435 | Controller.prototype._lookUp = function(query) {
436 | var _callback;
437 | _callback = function(queryCBId, data) {
438 | if (queryCBId !== this.expectedQueryCBId) {
439 | return;
440 | }
441 | if (data && data.length > 0) {
442 | return this.renderView(this.constructor.arrayToDefaultHash(data));
443 | } else {
444 | return this.view.hide();
445 | }
446 | };
447 | this.expectedQueryCBId = this._generateQueryCBId();
448 | return this.model.query(query.text, $.proxy(_callback, this, this.expectedQueryCBId));
449 | };
450 |
451 | return Controller;
452 |
453 | })();
454 |
455 | TextareaController = (function(superClass) {
456 | extend(TextareaController, superClass);
457 |
458 | function TextareaController() {
459 | return TextareaController.__super__.constructor.apply(this, arguments);
460 | }
461 |
462 | TextareaController.prototype.catchQuery = function() {
463 | var caretPos, content, end, isString, query, start, subtext;
464 | content = this.$inputor.val();
465 | caretPos = this.$inputor.caret('pos', {
466 | iframe: this.app.iframe
467 | });
468 | subtext = content.slice(0, caretPos);
469 | query = this.callbacks("matcher").call(this, this.at, subtext, this.getOpt('startWithSpace'));
470 | isString = typeof query === 'string';
471 | if (isString && query.length < this.getOpt('minLen', 0)) {
472 | return;
473 | }
474 | if (isString && query.length <= this.getOpt('maxLen', 20)) {
475 | start = caretPos - query.length;
476 | end = start + query.length;
477 | this.pos = start;
478 | query = {
479 | 'text': query,
480 | 'headPos': start,
481 | 'endPos': end
482 | };
483 | this.trigger("matched", [this.at, query.text]);
484 | } else {
485 | query = null;
486 | this.view.hide();
487 | }
488 | return this.query = query;
489 | };
490 |
491 | TextareaController.prototype.rect = function() {
492 | var c, iframeOffset, scaleBottom;
493 | if (!(c = this.$inputor.caret('offset', this.pos - 1, {
494 | iframe: this.app.iframe
495 | }))) {
496 | return;
497 | }
498 | if (this.app.iframe && !this.app.iframeAsRoot) {
499 | iframeOffset = $(this.app.iframe).offset();
500 | c.left += iframeOffset.left;
501 | c.top += iframeOffset.top;
502 | }
503 | scaleBottom = this.app.document.selection ? 0 : 2;
504 | return {
505 | left: c.left,
506 | top: c.top,
507 | bottom: c.top + c.height + scaleBottom
508 | };
509 | };
510 |
511 | TextareaController.prototype.insert = function(content, $li) {
512 | var $inputor, source, startStr, suffix, text;
513 | $inputor = this.$inputor;
514 | source = $inputor.val();
515 | startStr = source.slice(0, Math.max(this.query.headPos - this.at.length, 0));
516 | suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || " ";
517 | content += suffix;
518 | text = "" + startStr + content + (source.slice(this.query['endPos'] || 0));
519 | $inputor.val(text);
520 | $inputor.caret('pos', startStr.length + content.length, {
521 | iframe: this.app.iframe
522 | });
523 | if (!$inputor.is(':focus')) {
524 | $inputor.focus();
525 | }
526 | return $inputor.change();
527 | };
528 |
529 | return TextareaController;
530 |
531 | })(Controller);
532 |
533 | EditableController = (function(superClass) {
534 | extend(EditableController, superClass);
535 |
536 | function EditableController() {
537 | return EditableController.__super__.constructor.apply(this, arguments);
538 | }
539 |
540 | EditableController.prototype._getRange = function() {
541 | var sel;
542 | sel = this.app.window.getSelection();
543 | if (sel.rangeCount > 0) {
544 | return sel.getRangeAt(0);
545 | }
546 | };
547 |
548 | EditableController.prototype._setRange = function(position, node, range) {
549 | if (range == null) {
550 | range = this._getRange();
551 | }
552 | if (!range) {
553 | return;
554 | }
555 | node = $(node)[0];
556 | if (position === 'after') {
557 | range.setEndAfter(node);
558 | range.setStartAfter(node);
559 | } else {
560 | range.setEndBefore(node);
561 | range.setStartBefore(node);
562 | }
563 | range.collapse(false);
564 | return this._clearRange(range);
565 | };
566 |
567 | EditableController.prototype._clearRange = function(range) {
568 | var sel;
569 | if (range == null) {
570 | range = this._getRange();
571 | }
572 | sel = this.app.window.getSelection();
573 | if (this.ctrl_a_pressed == null) {
574 | sel.removeAllRanges();
575 | return sel.addRange(range);
576 | }
577 | };
578 |
579 | EditableController.prototype._movingEvent = function(e) {
580 | var ref;
581 | return e.type === 'click' || ((ref = e.which) === KEY_CODE.RIGHT || ref === KEY_CODE.LEFT || ref === KEY_CODE.UP || ref === KEY_CODE.DOWN);
582 | };
583 |
584 | EditableController.prototype._unwrap = function(node) {
585 | var next;
586 | node = $(node).unwrap().get(0);
587 | if ((next = node.nextSibling) && next.nodeValue) {
588 | node.nodeValue += next.nodeValue;
589 | $(next).remove();
590 | }
591 | return node;
592 | };
593 |
594 | EditableController.prototype.catchQuery = function(e) {
595 | var $inserted, $query, _range, index, inserted, isString, lastNode, matched, offset, query, query_content, range;
596 | if (!(range = this._getRange())) {
597 | return;
598 | }
599 | if (!range.collapsed) {
600 | return;
601 | }
602 | if (e.which === KEY_CODE.ENTER) {
603 | ($query = $(range.startContainer).closest('.atwho-query')).contents().unwrap();
604 | if ($query.is(':empty')) {
605 | $query.remove();
606 | }
607 | ($query = $(".atwho-query", this.app.document)).text($query.text()).contents().last().unwrap();
608 | this._clearRange();
609 | return;
610 | }
611 | if (/firefox/i.test(navigator.userAgent)) {
612 | if ($(range.startContainer).is(this.$inputor)) {
613 | this._clearRange();
614 | return;
615 | }
616 | if (e.which === KEY_CODE.BACKSPACE && range.startContainer.nodeType === document.ELEMENT_NODE && (offset = range.startOffset - 1) >= 0) {
617 | _range = range.cloneRange();
618 | _range.setStart(range.startContainer, offset);
619 | if ($(_range.cloneContents()).contents().last().is('.atwho-inserted')) {
620 | inserted = $(range.startContainer).contents().get(offset);
621 | this._setRange('after', $(inserted).contents().last());
622 | }
623 | } else if (e.which === KEY_CODE.LEFT && range.startContainer.nodeType === document.TEXT_NODE) {
624 | $inserted = $(range.startContainer.previousSibling);
625 | if ($inserted.is('.atwho-inserted') && range.startOffset === 0) {
626 | this._setRange('after', $inserted.contents().last());
627 | }
628 | }
629 | }
630 | $(range.startContainer).closest('.atwho-inserted').addClass('atwho-query').siblings().removeClass('atwho-query');
631 | if (($query = $(".atwho-query", this.app.document)).length > 0 && $query.is(':empty') && $query.text().length === 0) {
632 | $query.remove();
633 | }
634 | if (!this._movingEvent(e)) {
635 | $query.removeClass('atwho-inserted');
636 | }
637 | if ($query.length > 0) {
638 | switch (e.which) {
639 | case KEY_CODE.LEFT:
640 | this._setRange('before', $query.get(0), range);
641 | $query.removeClass('atwho-query');
642 | return;
643 | case KEY_CODE.RIGHT:
644 | this._setRange('after', $query.get(0).nextSibling, range);
645 | $query.removeClass('atwho-query');
646 | return;
647 | }
648 | }
649 | if ($query.length > 0 && (query_content = $query.attr('data-atwho-at-query'))) {
650 | $query.empty().html(query_content).attr('data-atwho-at-query', null);
651 | this._setRange('after', $query.get(0), range);
652 | }
653 | _range = range.cloneRange();
654 | _range.setStart(range.startContainer, 0);
655 | matched = this.callbacks("matcher").call(this, this.at, _range.toString(), this.getOpt('startWithSpace'));
656 | isString = typeof matched === 'string';
657 | if ($query.length === 0 && isString && (index = range.startOffset - this.at.length - matched.length) >= 0) {
658 | range.setStart(range.startContainer, index);
659 | $query = $('', this.app.document).attr(this.getOpt("editableAtwhoQueryAttrs")).addClass('atwho-query');
660 | range.surroundContents($query.get(0));
661 | lastNode = $query.contents().last().get(0);
662 | if (/firefox/i.test(navigator.userAgent)) {
663 | range.setStart(lastNode, lastNode.length);
664 | range.setEnd(lastNode, lastNode.length);
665 | this._clearRange(range);
666 | } else {
667 | this._setRange('after', lastNode, range);
668 | }
669 | }
670 | if (isString && matched.length < this.getOpt('minLen', 0)) {
671 | return;
672 | }
673 | if (isString && matched.length <= this.getOpt('maxLen', 20)) {
674 | query = {
675 | text: matched,
676 | el: $query
677 | };
678 | this.trigger("matched", [this.at, query.text]);
679 | return this.query = query;
680 | } else {
681 | this.view.hide();
682 | this.query = {
683 | el: $query
684 | };
685 | if ($query.text().indexOf(this.at) >= 0) {
686 | if (this._movingEvent(e) && $query.hasClass('atwho-inserted')) {
687 | $query.removeClass('atwho-query');
688 | } else if (false !== this.callbacks('afterMatchFailed').call(this, this.at, $query)) {
689 | this._setRange("after", this._unwrap($query.text($query.text()).contents().first()));
690 | }
691 | }
692 | return null;
693 | }
694 | };
695 |
696 | EditableController.prototype.rect = function() {
697 | var $iframe, iframeOffset, rect;
698 | rect = this.query.el.offset();
699 | if (this.app.iframe && !this.app.iframeAsRoot) {
700 | iframeOffset = ($iframe = $(this.app.iframe)).offset();
701 | rect.left += iframeOffset.left - this.$inputor.scrollLeft();
702 | rect.top += iframeOffset.top - this.$inputor.scrollTop();
703 | }
704 | rect.bottom = rect.top + this.query.el.height();
705 | return rect;
706 | };
707 |
708 | EditableController.prototype.insert = function(content, $li) {
709 | var data, range, suffix, suffixNode;
710 | suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || "\u00A0";
711 | data = $li.data('item-data');
712 | this.query.el.removeClass('atwho-query').addClass('atwho-inserted').html(content).attr('data-atwho-at-query', "" + data['atwho-at'] + this.query.text);
713 | if (range = this._getRange()) {
714 | range.setEndAfter(this.query.el[0]);
715 | range.collapse(false);
716 | range.insertNode(suffixNode = this.app.document.createTextNode("\u2060" + suffix));
717 | this._setRange('after', suffixNode, range);
718 | }
719 | if (!this.$inputor.is(':focus')) {
720 | this.$inputor.focus();
721 | }
722 | return this.$inputor.change();
723 | };
724 |
725 | return EditableController;
726 |
727 | })(Controller);
728 |
729 | Model = (function() {
730 | function Model(context) {
731 | this.context = context;
732 | this.at = this.context.at;
733 | this.storage = this.context.$inputor;
734 | }
735 |
736 | Model.prototype.destroy = function() {
737 | return this.storage.data(this.at, null);
738 | };
739 |
740 | Model.prototype.saved = function() {
741 | return this.fetch() > 0;
742 | };
743 |
744 | Model.prototype.query = function(query, callback) {
745 | var _remoteFilter, data, searchKey;
746 | data = this.fetch();
747 | searchKey = this.context.getOpt("searchKey");
748 | data = this.context.callbacks('filter').call(this.context, query, data, searchKey) || [];
749 | _remoteFilter = this.context.callbacks('remoteFilter');
750 | if (data.length > 0 || (!_remoteFilter && data.length === 0)) {
751 | return callback(data);
752 | } else {
753 | return _remoteFilter.call(this.context, query, callback);
754 | }
755 | };
756 |
757 | Model.prototype.fetch = function() {
758 | return this.storage.data(this.at) || [];
759 | };
760 |
761 | Model.prototype.save = function(data) {
762 | return this.storage.data(this.at, this.context.callbacks("beforeSave").call(this.context, data || []));
763 | };
764 |
765 | Model.prototype.load = function(data) {
766 | if (!(this.saved() || !data)) {
767 | return this._load(data);
768 | }
769 | };
770 |
771 | Model.prototype.reload = function(data) {
772 | return this._load(data);
773 | };
774 |
775 | Model.prototype._load = function(data) {
776 | if (typeof data === "string") {
777 | return $.ajax(data, {
778 | dataType: "json"
779 | }).done((function(_this) {
780 | return function(data) {
781 | return _this.save(data);
782 | };
783 | })(this));
784 | } else {
785 | return this.save(data);
786 | }
787 | };
788 |
789 | return Model;
790 |
791 | })();
792 |
793 | View = (function() {
794 | function View(context) {
795 | this.context = context;
796 | this.$el = $("");
797 | this.timeoutID = null;
798 | this.context.$el.append(this.$el);
799 | this.bindEvent();
800 | }
801 |
802 | View.prototype.init = function() {
803 | var id;
804 | id = this.context.getOpt("alias") || this.context.at.charCodeAt(0);
805 | return this.$el.attr({
806 | 'id': "at-view-" + id
807 | });
808 | };
809 |
810 | View.prototype.destroy = function() {
811 | return this.$el.remove();
812 | };
813 |
814 | View.prototype.bindEvent = function() {
815 | var $menu;
816 | $menu = this.$el.find('ul');
817 | return $menu.on('mouseenter.atwho-view', 'li', function(e) {
818 | $menu.find('.cur').removeClass('cur');
819 | return $(e.currentTarget).addClass('cur');
820 | }).on('click.atwho-view', 'li', (function(_this) {
821 | return function(e) {
822 | $menu.find('.cur').removeClass('cur');
823 | $(e.currentTarget).addClass('cur');
824 | _this.choose(e);
825 | return e.preventDefault();
826 | };
827 | })(this));
828 | };
829 |
830 | View.prototype.visible = function() {
831 | return this.$el.is(":visible");
832 | };
833 |
834 | View.prototype.highlighted = function() {
835 | return this.$el.find(".cur").length > 0;
836 | };
837 |
838 | View.prototype.choose = function(e) {
839 | var $li, content;
840 | if (($li = this.$el.find(".cur")).length) {
841 | content = this.context.insertContentFor($li);
842 | this.context._stopDelayedCall();
843 | this.context.insert(this.context.callbacks("beforeInsert").call(this.context, content, $li), $li);
844 | this.context.trigger("inserted", [$li, e]);
845 | this.hide(e);
846 | }
847 | if (this.context.getOpt("hideWithoutSuffix")) {
848 | return this.stopShowing = true;
849 | }
850 | };
851 |
852 | View.prototype.reposition = function(rect) {
853 | var _window, offset, overflowOffset, ref;
854 | _window = this.context.app.iframeAsRoot ? this.context.app.window : window;
855 | if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {
856 | rect.bottom = rect.top - this.$el.height();
857 | }
858 | if (rect.left > (overflowOffset = $(_window).width() - this.$el.width() - 5)) {
859 | rect.left = overflowOffset;
860 | }
861 | offset = {
862 | left: rect.left,
863 | top: rect.bottom
864 | };
865 | if ((ref = this.context.callbacks("beforeReposition")) != null) {
866 | ref.call(this.context, offset);
867 | }
868 | this.$el.offset(offset);
869 | return this.context.trigger("reposition", [offset]);
870 | };
871 |
872 | View.prototype.next = function() {
873 | var cur, next;
874 | cur = this.$el.find('.cur').removeClass('cur');
875 | next = cur.next();
876 | if (!next.length) {
877 | next = this.$el.find('li:first');
878 | }
879 | next.addClass('cur');
880 | return this.scrollTop(Math.max(0, cur.innerHeight() * (next.index() + 2) - this.$el.height()));
881 | };
882 |
883 | View.prototype.prev = function() {
884 | var cur, prev;
885 | cur = this.$el.find('.cur').removeClass('cur');
886 | prev = cur.prev();
887 | if (!prev.length) {
888 | prev = this.$el.find('li:last');
889 | }
890 | prev.addClass('cur');
891 | return this.scrollTop(Math.max(0, cur.innerHeight() * (prev.index() + 2) - this.$el.height()));
892 | };
893 |
894 | View.prototype.scrollTop = function(scrollTop) {
895 | var scrollDuration;
896 | scrollDuration = this.context.getOpt('scrollDuration');
897 | if (scrollDuration) {
898 | return this.$el.animate({
899 | scrollTop: scrollTop
900 | }, scrollDuration);
901 | } else {
902 | return this.$el.scrollTop(scrollTop);
903 | }
904 | };
905 |
906 | View.prototype.show = function() {
907 | var rect;
908 | if (this.stopShowing) {
909 | this.stopShowing = false;
910 | return;
911 | }
912 | if (!this.visible()) {
913 | this.$el.show();
914 | this.$el.scrollTop(0);
915 | this.context.trigger('shown');
916 | }
917 | if (rect = this.context.rect()) {
918 | return this.reposition(rect);
919 | }
920 | };
921 |
922 | View.prototype.hide = function(e, time) {
923 | var callback;
924 | if (!this.visible()) {
925 | return;
926 | }
927 | if (isNaN(time)) {
928 | this.$el.hide();
929 | return this.context.trigger('hidden', [e]);
930 | } else {
931 | callback = (function(_this) {
932 | return function() {
933 | return _this.hide();
934 | };
935 | })(this);
936 | clearTimeout(this.timeoutID);
937 | return this.timeoutID = setTimeout(callback, time);
938 | }
939 | };
940 |
941 | View.prototype.render = function(list) {
942 | var $li, $ul, i, item, len, li, tpl;
943 | if (!($.isArray(list) && list.length > 0)) {
944 | this.hide();
945 | return;
946 | }
947 | this.$el.find('ul').empty();
948 | $ul = this.$el.find('ul');
949 | tpl = this.context.getOpt('displayTpl');
950 | for (i = 0, len = list.length; i < len; i++) {
951 | item = list[i];
952 | item = $.extend({}, item, {
953 | 'atwho-at': this.context.at
954 | });
955 | li = this.context.callbacks("tplEval").call(this.context, tpl, item, "onDisplay");
956 | $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
957 | $li.data("item-data", item);
958 | $ul.append($li);
959 | }
960 | this.show();
961 | if (this.context.getOpt('highlightFirst')) {
962 | return $ul.find("li:first").addClass("cur");
963 | }
964 | };
965 |
966 | return View;
967 |
968 | })();
969 |
970 | KEY_CODE = {
971 | DOWN: 40,
972 | UP: 38,
973 | ESC: 27,
974 | TAB: 9,
975 | ENTER: 13,
976 | CTRL: 17,
977 | A: 65,
978 | P: 80,
979 | N: 78,
980 | LEFT: 37,
981 | UP: 38,
982 | RIGHT: 39,
983 | DOWN: 40,
984 | BACKSPACE: 8,
985 | SPACE: 32
986 | };
987 |
988 | DEFAULT_CALLBACKS = {
989 | beforeSave: function(data) {
990 | return Controller.arrayToDefaultHash(data);
991 | },
992 | matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
993 | var _a, _y, match, regexp, space;
994 | flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
995 | if (should_startWithSpace) {
996 | flag = '(?:^|\\s)' + flag;
997 | }
998 | _a = decodeURI("%C3%80");
999 | _y = decodeURI("%C3%BF");
1000 | space = acceptSpaceBar ? "\ " : "";
1001 | regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
1002 | match = regexp.exec(subtext);
1003 | if (match) {
1004 | return match[2] || match[1];
1005 | } else {
1006 | return null;
1007 | }
1008 | },
1009 | filter: function(query, data, searchKey) {
1010 | var _results, i, item, len;
1011 | _results = [];
1012 | for (i = 0, len = data.length; i < len; i++) {
1013 | item = data[i];
1014 | if (~new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase())) {
1015 | _results.push(item);
1016 | }
1017 | }
1018 | return _results;
1019 | },
1020 | remoteFilter: null,
1021 | sorter: function(query, items, searchKey) {
1022 | var _results, i, item, len;
1023 | if (!query) {
1024 | return items;
1025 | }
1026 | _results = [];
1027 | for (i = 0, len = items.length; i < len; i++) {
1028 | item = items[i];
1029 | item.atwho_order = new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase());
1030 | if (item.atwho_order > -1) {
1031 | _results.push(item);
1032 | }
1033 | }
1034 | return _results.sort(function(a, b) {
1035 | return a.atwho_order - b.atwho_order;
1036 | });
1037 | },
1038 | tplEval: function(tpl, map) {
1039 | var error, template;
1040 | template = tpl;
1041 | try {
1042 | if (typeof tpl !== 'string') {
1043 | template = tpl(map);
1044 | }
1045 | return template.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
1046 | return map[key];
1047 | });
1048 | } catch (_error) {
1049 | error = _error;
1050 | return "";
1051 | }
1052 | },
1053 | highlighter: function(li, query) {
1054 | var regexp;
1055 | if (!query) {
1056 | return li;
1057 | }
1058 | regexp = new RegExp(">\\s*(\\w*?)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig');
1059 | return li.replace(regexp, function(str, $1, $2, $3) {
1060 | return '> ' + $1 + '' + $2 + '' + $3 + ' <';
1061 | });
1062 | },
1063 | beforeInsert: function(value, $li) {
1064 | return value;
1065 | },
1066 | beforeReposition: function(offset) {
1067 | return offset;
1068 | },
1069 | afterMatchFailed: function(at, el) {}
1070 | };
1071 |
1072 | Api = {
1073 | load: function(at, data) {
1074 | var c;
1075 | if (c = this.controller(at)) {
1076 | return c.model.load(data);
1077 | }
1078 | },
1079 | isSelecting: function() {
1080 | var ref;
1081 | return !!((ref = this.controller()) != null ? ref.view.visible() : void 0);
1082 | },
1083 | hide: function() {
1084 | var ref;
1085 | return (ref = this.controller()) != null ? ref.view.hide() : void 0;
1086 | },
1087 | reposition: function() {
1088 | var c;
1089 | if (c = this.controller()) {
1090 | return c.view.reposition(c.rect());
1091 | }
1092 | },
1093 | setIframe: function(iframe, asRoot) {
1094 | this.setupRootElement(iframe, asRoot);
1095 | return null;
1096 | },
1097 | run: function() {
1098 | return this.dispatch();
1099 | },
1100 | destroy: function() {
1101 | this.shutdown();
1102 | return this.$inputor.data('atwho', null);
1103 | }
1104 | };
1105 |
1106 | $.fn.atwho = function(method) {
1107 | var _args, result;
1108 | _args = arguments;
1109 | result = null;
1110 | this.filter('textarea, input, [contenteditable=""], [contenteditable=true]').each(function() {
1111 | var $this, app;
1112 | if (!(app = ($this = $(this)).data("atwho"))) {
1113 | $this.data('atwho', (app = new App(this)));
1114 | }
1115 | if (typeof method === 'object' || !method) {
1116 | return app.reg(method.at, method);
1117 | } else if (Api[method] && app) {
1118 | return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
1119 | } else {
1120 | return $.error("Method " + method + " does not exist on jQuery.atwho");
1121 | }
1122 | });
1123 | if (result != null) {
1124 | return result;
1125 | } else {
1126 | return this;
1127 | }
1128 | };
1129 |
1130 | $.fn.atwho["default"] = {
1131 | at: void 0,
1132 | alias: void 0,
1133 | data: null,
1134 | displayTpl: "${name}",
1135 | insertTpl: "${atwho-at}${name}",
1136 | callbacks: DEFAULT_CALLBACKS,
1137 | searchKey: "name",
1138 | suffix: void 0,
1139 | hideWithoutSuffix: false,
1140 | startWithSpace: true,
1141 | highlightFirst: true,
1142 | limit: 5,
1143 | maxLen: 20,
1144 | minLen: 0,
1145 | displayTimeout: 300,
1146 | delay: null,
1147 | spaceSelectsMatch: false,
1148 | tabSelectsMatch: true,
1149 | editableAtwhoQueryAttrs: {},
1150 | scrollDuration: 150,
1151 | suspendOnComposing: true,
1152 | lookUpOnClick: true
1153 | };
1154 |
1155 | $.fn.atwho.debug = false;
1156 |
1157 |
1158 | }));
1159 |
--------------------------------------------------------------------------------