At.js-1.5.4/000077500000000000000000000000001311725210300124735ustar00rootroot00000000000000At.js-1.5.4/.codoopts000066400000000000000000000002201311725210300143200ustar00rootroot00000000000000--name "Codo" --readme README.md --title "At.js Documentation" --private --quiet --output-dir ./doc ./src - LICENSE CHANGELOG.md At.js-1.5.4/.gitignore000066400000000000000000000001271311725210300144630ustar00rootroot00000000000000*.swp .DS_Store doc/ node_modules/ _* bower_components/ components .grunt *.log build/ At.js-1.5.4/.travis.yml000066400000000000000000000004251311725210300146050ustar00rootroot00000000000000language: node_js node_js: - "4" notifications: email: on_success: never # default: change on_failure: change # default: always branches: only: - master - stable - beta before_script: - npm install -g gulp - npm install -g bower - bower install At.js-1.5.4/CHANGELOG.md000066400000000000000000000252131311725210300143070ustar00rootroot00000000000000### v1.5.0 add `headerTpl` settings * 7a41d93 - #375 from vcekov/fix_scroll_position - Valentin Cekov * ecbf34f - #373 from vcekov/val/fix_key_navigation_interefence_with_mouse - Valentin Cekov * b68cf84 - #364 from WorktileTech/master - Harold.Luo * f836f04 - #372 from vcekov/fix_caret_for_space_after_@ - Harold.Luo * 06cf6bb - Properly set caret position after failed match - Valentin Cekov * c9ed2e2 - support header template. - htz ### v1.4.0 #### Contenteditable Pressing `Backspace` will turn the inserted element back to the origin query 'moment'. * 84edc9f - skip inserted element when moving left or right - ichord * 25a61d3 - the jQuery npm package is now called jquery. Fixes #338 - Mick Staugaard * 03ed71f - Merge pull request #351 from mociepka/master - Harold.Luo * ae00dc3 - Point main script in package json - Michał Ociepka * c5f31f5 - Merge branch 'dev' into HEAD - ichord * c399397 - fix contenteditable cursor bug when typing "a" into query - ichord * 7f4295a - fix previous replacements get clobbered when re-intering the inserted element - ichord * f00fabd - Merge pull request #354 from lvegerano/master - Harold.Luo * a42065e - Adds guard to event and dist file - Luis Vegerano * e4aaa30 - Add option to disable loopUp on click - Luis Vegerano * c9b7609 - Fix bug where callbacks would run before reaching minLen. Fixes #329. - Mike Leone * f8692dc - Add support for minLen. Connects to issue #316. - Mike Leone * fd7d298 - FIX: the value of `isSelecting` - ichord * c374c93 - FIX: IME typing error - ichord ### v1.3.0 * 7f2189d - fix #294 inserts "" suffix in contenteditable * bae95d9 - add `tabSelectsMatch` setting to make tab selection optional * e966aba - Merge pull request #298 from kkirsche/patch-1 - Harold.Luo * 9f78239 - Remove moot `version` property from bower.json - Kevin Kirsche ### v1.2.0 db09ac7 -> 886613f * 886613f - add `$.fn.atwho.debug = false` to trigger debug mode * 6567af9 - Enable default events when nothing is highlighted - Teemu * 752ad4a - Add scrollDuration option. - Takuru * bf17d43 - add parameter to allow for a spacebar in the middle of a search so that you can match a first + last name, for example - Feather Knee * a1d5fe7 - add `reposition` API - ichord * 9bcb06e - add "onInsert", "onDispaly" arguments to `tplEval` - ichord * db09ac7 - add `hide` api - ichord ### v1.1.0 * lisafeather/displyTplCallBack - #259 * ADD: `editableAtwhoQueryAttrs` options * Added setting for 'spaceSelectsMatch' (default false/off) ### v1.0.0 **The naming convention are using camel case**. It means that every callback and setting's name are switched from underscope_naming to CamelNaming. Sorry about this. Future version's naming will follow the rules of http://semver.org constantly. #### Options: * Replaced `tpl` with `displayTpl`: display template of dropdown menu items. In previous versions, At.js will fetch the value of `data-value` to insert; It stops doing it. Please use the `insertTpl` option to manage the content to insert instead. The default value is `"
  • ${name}
  • "` * The `insertTpl` option will be used in *textarea* as well. The default value is `"${atwho-at}${name}"` #### Callbacks: * Added `afterMatchFailed` callback to *contentEditable* It will be invoked after fail to match any query and stopping matching. Open *examples/hashtas.html* to examine how it work. * Removed `inserting_wrapper` callback to *contentEditable* #### Internal changes: * refactor the `Controller` Introduced `EditableController` class to control actions of `contenteditable` element. Introduced `TextareaController` class to control actions of `textarea` element. Both of them are inherit from the `Controller` class. * Refactored contentEditable mode Inserted content are wrapped in a span: `` Querying content are wrapped in a span: `` * Bring back auto-discovery to iframe. * Fix wrong offset in iframe * Replaced `iframeStandalone` with `iframeAdRoot` * All processed events are preventing default and stopping propagation. ### v0.5.2 * e1f6566 - fix error that doesn't display mention list on new line * 8fe3a54 - can insert multiple node from `inserting_wrapper` * 4080151 - scroll to top after showing * 01555f8 - scroll long dropdown list * 1b8999d - Add spm support * f2b8e9c - change name in package.json * b61bfdc - search on click * b1efd09 - Fixes error with selecting always first item on the list on iOS WebView when using https://github.com/ftlabs/fastclick * 7ed2890 - Allow accented characters in matcher ### v0.5.1 * 219de3d - fix Goes off screen / gets cropped if there isn't enough room * 1100c5b - No longer inherits text colour from document * ce60958 - on more boolean argument for `setIframe` api to work cross-document issues #199 ### v0.5.0 * 593893c - refactor inserting of contenteditable Adding `inserting_wrapper` for customize wrapping inserting content. Not to insert item as a block in Firefox. check out issue #109. Removing `getInsertedItems`, `getInsertedIDs` API. You have to collect them on your own. * 4d3fb8f - have to set IFRAME manually * 1f13a16 - change space_after to suffix * b099ebb - fix caret position error after inserting * 2c47d7a - fix #178 hide view while clicking somewhere else ### v0.4.12 * eeafab1 - fix error: will always call hidden atwho event * b0f6ceb - Highlighter finds the first occurrence * da256db - Adds possibility of having empty prefix (at keyword) in controllers * b884225 - add `space_after` option * 65d6273 - Passes esc/tab/return keyup events through to emitted hide event ### v0.4.11 * bf938db - add `delay` setting, support delay searching * a0b5a6f - fix bug: terminate if query out of max_len * 01d6d5b - add css min file ### v0.4.10 * update jquery dependence version ### v0.4.9 * f317bd7 not lowercase query, add `highlight_first` option ### v0.4.8 * 79bbef4 destroy atwho view container dom * 0372d65 update bower and component keywords * 52a41f5 add optional `before_repostion` callback * cc1c239 Fixes #143 - ichord ### v0.4.7 * resolved #133, #135, #137. * add `beforeDestroy` event * wouldn't concat `caret.js` into `dist/js/jquery.atwho.js` any more. * seperate `jquery.atwho.coffee` into pieces. * seperate testing. ### v0.4.6 * 2d9ab23 fix `wrong document` error in IE iframe ### v0.4.5 * 664a765 support iframe ### v0.4.4 * 9ac7e75 - improve contentEditable for IE 8 It's still some bugs in IE 8, just DON'T use it I don't want to spend more time on IE 8. So it would be the ending fixup. And i will still leave related code for a while maybe in case anyone want to help to improve it. Just encourge your users to upgrate the browers or just switch to a batter one please !! * a8371b3 - move project page to master from gh-pages. * 24b6225 - fix bugs #122 * 645e030 - update Caret.js to v0.0.5 ### v0.4.3 * e8e7561 update `Caret.js` to `v0.0.4` ### v0.4.2 * 4169b74 - binding data storage to the inputor. issues #121 * 11d053f - reduse querying twice. issues#112 ### v0.4.1 * b7721be - fix bug at view id was not been assign. close issues #99 * 407f069 - fix bug: Can not autofocus after click the at-list in FireFox. #95 * 917f033 - fix bug: click do not work in div-contenteditable. close issues #93 ### v0.4.0 * update `Caret.js` to `v0.0.2` * `contenteditable` support !! * change content of default item template `tpl` * new rule to insert the `at` : will always remove the `at` from inputor but will add it back from `tpl` in default. so, if you are using your own `tpl` and want to show the `at` char, you have to do it yourself. * add `insert_tpl` setting for `contenteditable`. it will insert `data-value` of li element that eval from `tpl` in default. * new APIs for `contenteditable`: `getInsertedItemsWithIDs`, `getInsertedItems`, `getInsertedIDs` ### 2013-08-07 - v0.3.2 * bower * remove `Caret.js` codes and add it as bower dependencies * remove `display_flag` settings. * add `start_with_space` settings, default `true` * change `super_call` function to `call_default` ### 2013-04-28 * release new api `load`, `run` * add `alias` setting for `load` data or as the view's id * matching key with a space before it * register key in settings `{at: "@", data: []}` instead of being a argument * `max_len` setting for max length to search * change the default matcher regrex rule: occur at start of line or after whitespace * will not sort the datay without valid query string ### 2013-04-23 * group all data handlers as `Model` class. * All callbacks's context would be current `Controller` ### 2013-04-05 * `data` setting will be used to load data either local or remote. If it's String as URL it will preload data from remote by launch a ajax request (every times At.js call `reg` to update settings) * remove default `remote_filter` from callbacks list. * add `get_data` and `save_data` function to contoller. They are used to get and save whole data for At.js * `save_data` will invoke `data_refactor` everytime * will filter local data which is set in `settings` first and if it get nothing then call `remote_filter` if it's exists in callbacks list that is set by user. ### 2013-04 * remove ability of changing common setting after inputor binded * can fix list view after matched query in IE now. * separated core function (get offset of inputor) as a jquery plugins. ### v0.2.0 - 2012-12 **No more testing in IEs browsers.** #### Note The name `atWho` was changed to `atwho`. #### New features * Customer data handlers(matcher, filter, sorter) and template renders(highlight, template eval) by a group of configurable callbacks. * Support **AMD** #### Removed features * Filter by local data and remote (by ajax) data at the same time. * Caching * Mouse event #### Changed settings `-` mean removed option `+` mean new added option The one that start without `-` or `+` mean not change. * `-` data: [], * `+` data: null, * `-` choose: "data-value", * `+` search_key: "name", * `-` callback: null, * `+` callbacks: DEFAULT_CALLBACKS, * `+` display_timeout: 300, * `-` tpl: _DEFAULT_TPL * `+` tpl: DEFAULT_TPL * `-` cache: false Not change settings * cache: true, * limit: 5, * display_flag: true, ### v0.1.7 同步 `jquery-atwho-rails` gem 的版本号 这会是 `v0.1` 的固定版本. 不再有新功能更新. ###v0.1.2 2012-3-23 * box showing above instead of bottom when it get close to the bottom of window * coffeescript here is. * every registered character able to have thire own options such as template(`tpl`) * every inputor (textarea, input) able to have their own registered character and different behavior even the same character to other inputor ###v0.1.0 * 可以監聽多個字符 multiple char listening. * 顯示缺省列表. show default list. At.js-1.5.4/CONTRIBUTING.md000066400000000000000000000031731311725210300147300ustar00rootroot00000000000000## Contributing ### Code style **Two** space indent ### Modifying the code First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed. Test that gulp is installed globally by running `grunt -v` at the command-line. If gulp isn't installed globally, run `npm install -g gulp` to install the latest version. * Fork and clone the repo. * Run `npm install` and `bower install` to install all dev dependencies (including grunt). * Modify the `*.coffee` file. * Run `gulp` to build this project. Assuming that you don't see any red, you're ready to go. Just be sure to run `gulp` after making any changes, to ensure that nothing is broken. ### Submitting pull requests 1. Create a new branch, please don't work in your `master` branch directly. 1. Add failing tests for the change you want to make. Run `gulp` to see the tests fail. 1. Fix stuff. 1. Run `gulp` to see if the tests pass. Repeat steps 2-4 until done. 1. Open `_SpecRunner.html` unit test file(s) in actual browser to ensure tests pass everywhere. 1. Update the documentation to reflect any changes. 1. Push to your fork and submit a pull request. ### notes Please don't edit files in the `dist` subdirectory and *.js files in `src` as they are generated via gulp. You'll find source code in the `src` subdirectory! use `bower install` or `component install` to install dependencies first. ### PhantomJS While gulp can run the included unit tests via [PhantomJS](http://phantomjs.org/), this shouldn't be considered a substitute for the real thing. Please be sure to test the `_SpecRunner.html` unit test file(s) in _actual_ browsers. At.js-1.5.4/LICENSE-MIT000066400000000000000000000020471311725210300141320ustar00rootroot00000000000000Copyright (c) 2013 chord.luo@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. At.js-1.5.4/README.md000066400000000000000000000043131311725210300137530ustar00rootroot00000000000000**An autocompletion library to autocomplete mentions, smileys etc. just like on Github!** [![Build Status](https://travis-ci.org/ichord/At.js.png)](https://travis-ci.org/ichord/At.js) #### Notice At.js now **depends on** [Caret.js](https://github.com/ichord/Caret.js). Please read [**CHANGELOG.md**](CHANGELOG.md) for more details if you are going to update to new version. ### Demo http://ichord.github.com/At.js ### Documentation https://github.com/ichord/At.js/wiki ### Compatibility * `textarea` - Chrome, Safari, Firefox, IE7+ (maybe IE6) * `contentEditable` - Chrome, Safari, Firefox, IE9+ ### Features Preview * Support IE 7+ for **textarea**. * Supports HTML5 [**contentEditable**](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_Editable) elements (NOT including IE 8) * Can listen to any character and not just '@'. Can set up multiple listeners for different characters with different behavior and data * Listener events can be bound to multiple inputors. * Format returned data using templates * Keyboard controls in addition to mouse - `Tab` or `Enter` keys select the value - `Up` and `Down` navigate between values (and `Ctrl-P` and `Ctrl-N` also) - `Right` and `left` will re-search the keyword. * Custom data handlers and template renderers using a group of configurable callbacks * Supports AMD ### Requirements * jQuery >= 1.7.0. * [Caret.js](https://github.com/ichord/Caret.js) (You can use `Component` or `Bower` to install it.) ### Integrating with your Application Simply include the following files in your HTML and you are good to go. ```html ``` ```javascript $('#inputor').atwho({ at: "@", data:['Peter', 'Tom', 'Anne'] }) ``` #### Bower & Component For installing using Bower you can use `jquery.atwho` and for Component please use `ichord/At.js`. #### Rails You can include At.js in your `Rails` application using the gem [jquery-atwho-rails](https://github.com/ichord/jquery-atwho-rails). ### Core Team Members * [@ichord](https://twitter.com/_ichord) (twitter) At.js-1.5.4/bower.json000066400000000000000000000007761311725210300145160ustar00rootroot00000000000000{ "name": "At.js", "version": "1.5.4", "main": [ "dist/js/jquery.atwho.js", "dist/css/jquery.atwho.css" ], "ignore": [ "**/.*", "node_modules", "components", "libs", "spec" ], "dependencies": { "jquery": ">=1.7.0", "Caret.js": "~0.2.2" }, "devDependencies": { "jasmine-jquery": "~2.0.2" }, "keywords": [ "mention", "mentions", "autocomplete", "autocompletion", "autosuggest", "autosuggestion", "atjs", "at.js" ] } At.js-1.5.4/component.json000066400000000000000000000011571311725210300153740ustar00rootroot00000000000000{ "name": "At.js", "repo": "ichord/At.js", "description": "Add Github like mentions autocomplete to your application.", "version": "1.5.4", "demo": "http://ichord.github.com/At.js", "dependencies": { "ichord/Caret.js": "~0.2.2", "component/jquery": ">= 1.7.0" }, "main": [ "dist/js/jquery.atwho.js" ], "scripts": [ "dist/js/jquery.atwho.js" ], "styles": [ "dist/css/jquery.atwho.css" ], "license": "MIT", "keywords": [ "mentions", "ui", "mentions", "autocomplete", "autocompletion", "autosuggest", "autosuggestion", "atjs", "at.js" ] }At.js-1.5.4/examples/000077500000000000000000000000001311725210300143115ustar00rootroot00000000000000At.js-1.5.4/examples/cross_document/000077500000000000000000000000001311725210300173405ustar00rootroot00000000000000At.js-1.5.4/examples/cross_document/dataFrame.html000066400000000000000000000020671311725210300221170ustar00rootroot00000000000000 Data Iframe At.js-1.5.4/examples/cross_document/index.html000066400000000000000000000033651311725210300213440ustar00rootroot00000000000000 At.js

    At.js

    Cross-Document

    At.js-1.5.4/examples/cross_document/viewFrame.html000066400000000000000000000003321311725210300221510ustar00rootroot00000000000000 View Iframe

    hello!

    At.js-1.5.4/examples/hashtags.html000066400000000000000000000037511311725210300170070ustar00rootroot00000000000000 At.js

    Type `#` to autocomplete tags

    At.js-1.5.4/examples/medium-editor.html000066400000000000000000000035251311725210300177500ustar00rootroot00000000000000 At.js

    Example for medium-editor

    Easy! You should check out MoxieManager!
    At.js-1.5.4/examples/style.css000066400000000000000000000015251311725210300161660ustar00rootroot00000000000000html, body { background:#F9F9F9; padding: 0; margin: 0; font: 14px/1.6 "Lucida Grande", "Helvetica", sans-serif; color: #333; } h1,h2,h3,h4 { font-family: 'PT Sans', sans-serif; line-height: 40px; color: inherit; font-weight: bold; margin: 10px 0; text-rendering: optimizelegibility; } h2,h3 { color: gray; } strong { color: #424242; } a { color: #4183C4; text-decoration: none; } a:hover { text-decoration: underline; } .wrapper { width: 750px; padding: 20px; margin: 0 auto; } header { margin-top:30px; } .inputor { height: 160px; width: 90%; border: 1px solid #dadada; border-radius: 4px; padding: 5px 8px; outline: 0 none; margin: 10px 0; background: white; font-size: inherit; overflow-y: scroll; } .inputor:focus { border: 1px solid rgb(6, 150, 247); } footer { margin: 30px 0; } At.js-1.5.4/examples/tinyMCE.html000066400000000000000000000036471311725210300165210ustar00rootroot00000000000000 At.js

    Example for tinyMCE editor

    At.js-1.5.4/examples/ueditor.html000066400000000000000000000027351311725210300166610ustar00rootroot00000000000000 ueditor At.js-1.5.4/gulpfile.js000066400000000000000000000061771311725210300146530ustar00rootroot00000000000000var gulp = require('gulp'), coffee = require('gulp-coffee'), concat = require('gulp-concat'), umd = require('gulp-umd'), uglify = require('gulp-uglify'), rename = require("gulp-rename"), cssmin = require('gulp-cssmin'), jasmine = require('gulp-jasmine-phantom'), bump = require('gulp-bump'), header = require('gulp-header'), debug = require('gulp-debug'), util = require('gulp-util'); var name = 'jquery.atwho'; gulp.task('coffee', function() { gulp.src('src/*.coffee') .pipe(coffee({bare: true}).on('error', util.log)) .pipe(gulp.dest('./build/js')); }); gulp.task('concat', function() { fileList = [ 'build/js/default.js', 'build/js/app.js', 'build/js/controller.js', 'build/js/textareaController.js', 'build/js/editableController.js', 'build/js/model.js', 'build/js/view.js', 'build/js/api.js' ] gulp.src(fileList) .pipe(concat(name + ".js")) .pipe(gulp.dest('build')); }); gulp.task('umd', function() { gulp.src('build/' + name + ".js") .pipe(umd({template: "umd.template.js"})) .pipe(gulp.dest('build/js')); }); gulp.task('bump', function() { gulp.src(['bower.json', 'component.json', 'package.json']) .pipe(bump({version: "1.5.4"})) .pipe(gulp.dest('./')); }); gulp.task("mark", function() { var pkg = require('./package.json'); var banner = ['/**', ' * <%= pkg.name %> - <%= pkg.version %>', ' * Copyright (c) <%= year %> <%= pkg.author.name %> <<%= pkg.author.email %>>;', ' * Homepage: <%= pkg.homepage %>', ' * License: <%= pkg.license %>', ' */', ''].join('\n'); gulp.src('build/js/' + name + '.js') .pipe(header(banner, { pkg : pkg, year: (new Date).getFullYear()})) .pipe(gulp.dest('dist/js/')) }); gulp.task('compress', function() { gulp.src('dist/js/' + name + '.js') .pipe(uglify()) .pipe(rename({suffix: '.min'})) .pipe(gulp.dest('dist/js')); gulp.src('src/jquery.atwho.css').pipe(gulp.dest('dist/css')) gulp.src('dist/css/' + name + '.css') .pipe(cssmin()) .pipe(rename({suffix: '.min'})) .pipe(gulp.dest('dist/css')); }); gulp.task('test', function () { gulp.src('spec/**/*.coffee') .pipe(coffee({bare: true}).on('error', util.log)) .pipe(debug({title: "compiled specs"})) .pipe(gulp.dest('spec/build')) gulp.src('spec/build/javascripts/*.spec.js') .pipe(jasmine({ integration: true, specHtml: "specRunner.html" /* TODO: have to add css to spec vendor: [ 'bower_components/jquery/dist/jquery.js', 'bower_components/Caret.js/dist/jquery.caret.js', 'dist/js/jquery.atwho.js', 'node_modules/jasmine-jquery/lib/*.js', 'node_modules/jasmine-ajax/lib/*.js', 'spec/helpers/*.js', 'spec/build/spec_helper.js' ], */ })); }); gulp.task('compile', ['coffee', 'umd', 'concat']); gulp.task('default', ['compile', 'bump', 'mark', 'compress']); At.js-1.5.4/index.html000066400000000000000000000161521311725210300144750ustar00rootroot00000000000000 At.js

    At.js

    And!! it support ContentEditable mode too!!

    Try here now! :h

    At.js-1.5.4/package.json000066400000000000000000000022031311725210300147560ustar00rootroot00000000000000{ "name": "at.js", "main": "dist/js/jquery.atwho.js", "author": { "name": "chord.luo", "email": "chord.luo@gmail.com" }, "homepage": "http://ichord.github.com/At.js", "license": "MIT", "version": "1.5.4", "repository": { "type": "git", "url": "https://github.com/ichord/At.js" }, "engines": { "node": ">= 0.6.0" }, "scripts": { "test": "gulp test" }, "peerDependencies": { "jquery": ">=1.7.0 <4.0.0" }, "devDependencies": { "gulp": "^3.9.0", "gulp-bump": "^1.0.0", "gulp-coffee": "^2.3.1", "gulp-concat": "^2.6.0", "gulp-cssmin": "^0.1.7", "gulp-debug": "^2.1.2", "gulp-header": "^1.7.1", "gulp-jasmine": "^2.2.1", "gulp-jasmine-phantom": "^2.0.1", "gulp-rename": "^1.2.2", "gulp-uglify": "^1.5.1", "gulp-umd": "^0.2.0", "gulp-util": "^3.0.7", "jasmine-ajax": "^3.2.0", "jasmine-jquery": "^2.1.1", "phantomjs": "^1.9.19" }, "spm": { "main": "dist/js/jquery.atwho.js", "dependencies": { "jquery": ">=1.7.2", "caret.js": "~0.2.2" }, "ignore": [ "examples", "spec", "src" ] } } At.js-1.5.4/spec/000077500000000000000000000000001311725210300134255ustar00rootroot00000000000000At.js-1.5.4/spec/helpers/000077500000000000000000000000001311725210300150675ustar00rootroot00000000000000At.js-1.5.4/spec/helpers/noConflict.js000066400000000000000000000000241311725210300175170ustar00rootroot00000000000000jQuery.noConflict();At.js-1.5.4/spec/javascripts/000077500000000000000000000000001311725210300157565ustar00rootroot00000000000000At.js-1.5.4/spec/javascripts/apis.spec.coffee000066400000000000000000000046141311725210300210210ustar00rootroot00000000000000$inputor = null app = null describe "api", -> $ = jQuery beforeEach -> loadFixtures("inputors.html") $inputor = $("#inputor").atwho at: "@", data: fixtures["names"] app = getAppOf $inputor afterEach -> $inputor.atwho 'destroy' describe "inner", -> controller = null callbacks = null beforeEach -> jasmine.Ajax.install() controller = app.controller() afterEach -> jasmine.Ajax.uninstall() it "can get current data", -> simulateTypingIn $inputor expect(controller.model.fetch().length).toBe 24 it "can save current data", -> simulateTypingIn $inputor data = [{id: 1, name: "one"}, {id: 2, name: "two"}] controller.model.save(data) expect(controller.model.fetch().length).toBe 2 it "don't change data setting while using remote filter", -> $inputor.atwho at: "@" data: "/atwho.json" simulateTypingIn $inputor request = jasmine.Ajax.requests.mostRecent() response_data = [{"name":"Jacob"}, {"name":"Joshua"}, {"name":"Jayden"}] request.respondWith status: 200 responseText: JSON.stringify(response_data) expect(controller.getOpt("data")).toBe "/atwho.json" expect(controller.model.fetch().length).toBe 3 describe "public", -> controller = null data = [] beforeEach -> controller = app.controller() data = [ {one: 1} {two: 2} {three: 3} ] it "can load data for special flag", -> $inputor.atwho "load", "@", data expect(controller.model.fetch().length).toBe data.length it "can load data with alias", -> $inputor.atwho at: "@", alias: "at" $inputor.atwho "load", "at", data expect(controller.model.fetch().length).toBe data.length it "can run it handly", -> app.setContextFor null $inputor.caret('pos', 31) $inputor.atwho "run" expect(app.controller().view.$el).not.toBeHidden() it 'destroy', -> $inputor.atwho at: "~" view_id = app.controller('~').view.$el.attr('id') $inputor.atwho 'destroy' expect($("##{view_id}").length).toBe 0 expect($inputor.data('atwho')).toBe null expect($inputor.data('~')).toBe null it 'isSelecting correctness', -> expect($inputor.atwho 'isSelecting').toBe false simulateTypingIn $inputor expect($inputor.atwho 'isSelecting').toBe true At.js-1.5.4/spec/javascripts/content_editable.spec.coffee000066400000000000000000000020011311725210300233540ustar00rootroot00000000000000describe "content editable", -> $inputor = null app = null $ = jQuery beforeEach -> loadFixtures "inputors.html" $inputor = $("#editable").atwho at: "@", data: ["Jobs"] editableAtwhoQueryAttrs: {class: "hello", "data-editor-verified":true} app = getAppOf $inputor afterEach -> $inputor.atwho 'destroy' it "can insert content", -> triggerAtwhoAt $inputor expect($inputor.text()).toContain('@Jobs') it "insert by click", -> simulateTypingIn $inputor $inputor.blur() app.controller().view.$el.find('ul').children().first().trigger('click') expect($inputor.text()).toContain('@Jobs') it "unwrap span.atwho-query after match failed", -> simulateTypingIn $inputor expect $('.atwho-query').length .toBe 1 $('.atwho-query').html "@J " simulateTypingIn $inputor, "@", 3 expect $('.atwho-query').length .toBe 0 it "wrap span.atwho-query with customize attrs", -> # for #235 simulateTypingIn $inputor expect $('.atwho-query').data('editor-verified') .toBe true At.js-1.5.4/spec/javascripts/custom_callbacks.spec.coffee000066400000000000000000000046401311725210300233750ustar00rootroot00000000000000describe "custom callbacks", -> $inputor = null $ = jQuery beforeEach -> loadFixtures("inputors.html") afterEach -> $inputor.atwho 'destroy' describe "remoteFilter()", -> it "only renders the view for data from the latest lookup", -> callbackList = [] remoteFilter = jasmine.createSpy("remoteFilter").and.callFake (_, cb) -> callbackList.push cb $inputor = $("#inputor").atwho({ at: "@", data: [], callbacks: { remoteFilter } }) $inputor.val('@foo') app = getAppOf $inputor controller = app.controller() spyOn(controller, 'renderView') simulateTypingIn $inputor expect(remoteFilter).toHaveBeenCalled() simulateTypingIn $inputor expect(callbackList.length).toBeGreaterThan(1) while callbackList.length > 1 callbackList.shift()(['no renders']) expect(controller.renderView).not.toHaveBeenCalled() callbackList.shift()(['render']) expect(controller.renderView).toHaveBeenCalled() it "does not attempt to render the view after query has been cleared", -> remoteFilterCb = null remoteFilter = jasmine.createSpy("remoteFilter").and.callFake (_, cb) -> remoteFilterCb = cb $inputor = $("#inputor").atwho({ at: "@", data: [], callbacks: { remoteFilter } }) app = getAppOf $inputor controller = app.controller() spyOn controller, 'renderView' simulateTypingIn $inputor expect(remoteFilter).toHaveBeenCalled() $inputor.val '' simulateTypingIn $inputor expect(remoteFilter.calls.count()).toEqual(1) remoteFilterCb ['should not render'] expect(controller.renderView).not.toHaveBeenCalled() it "does not attempt to render the view after focus has been lost", -> remoteFilterCb = null remoteFilter = jasmine.createSpy("remoteFilter").and.callFake (_, cb) -> remoteFilterCb = cb $inputor = $("#inputor").atwho({ at: "@", data: [], callbacks: { remoteFilter } }) app = getAppOf $inputor controller = app.controller() spyOn controller, 'renderView' simulateTypingIn $inputor expect(remoteFilter).toHaveBeenCalled() $inputor.blur(); remoteFilterCb ['should not render'] expect(controller.renderView).not.toHaveBeenCalled() At.js-1.5.4/spec/javascripts/default_callbacks.spec.coffee000066400000000000000000000106201311725210300235020ustar00rootroot00000000000000 $inputor = null app = null describe "default callbacks", -> $ = jQuery callbacks = null text = null beforeEach -> loadFixtures("inputors.html") $inputor = $("#inputor").atwho at: "@", data: fixtures["names"] app = getAppOf $inputor beforeEach -> text = $.trim $inputor.text() callbacks = $.fn.atwho.default.callbacks app = $inputor.data("atwho") afterEach -> $inputor.atwho 'destroy' it "refactor the data before save", -> items = callbacks.beforeSave.call(app.controller(), fixtures["names"]) expect(items).toContain({"name":"Jacob"}) expect(items).toContain({"name":"Isabella"}) it "should match the key word following @", -> query = callbacks.matcher.call(app, "@", text) expect(query).toBe("Jobs") it "should not match a space following @ if acceptSpaceBar flag omitted", -> $inputor = $("#inputor").atwho at: "@", data: fixtures["names"] text = $.trim $inputor.text() query = callbacks.matcher.call(app, "@", text) expect(query).toBe("Jobs") it "should not match a space following @ if acceptSpaceBar flag false", -> $inputor = $("#inputor").atwho at: "@", data: fixtures["names"], acceptSpaceBar: false text = $.trim $inputor.text() query = callbacks.matcher.call(app, "@", text, false, false) expect(query).toBe("Jobs") it "should match a space following @ if acceptSpaceBar flag set to true", -> $inputor = $("#inputor4").atwho at: "@", data: fixtures["names"], acceptSpaceBar: true text = $.trim $inputor.text() query = callbacks.matcher.call(app, "@", text, false, true) expect(query).toBe("Jobs Blobs") it "should match the key word fllowing @ with specials chars", -> $inputor = $("#special-chars").atwho at: "@", data: fixtures["names"] text = $.trim $inputor.text() query = callbacks.matcher.call(app, "@", text) expect(query).toBe(decodeURI("J%C3%A9r%C3%A9m%C3%BF")) it "can filter data", -> names = callbacks.beforeSave.call(app.controller(), fixtures["names"]) names = callbacks.filter.call(app, "jo", names, "name") expect(names).toContain name: "Joshua" it "can filter numeric data", -> numerics = callbacks.beforeSave.call(app.controller(), fixtures["numerics"]) numerics = callbacks.filter.call(app, "1", numerics, "name") expect(numerics).toContain name: 10 it "request data from remote by ajax if set remoteFilter", -> remote_call = jasmine.createSpy("remote_call") $inputor.atwho at: "@" data: null, callbacks: remoteFilter: remote_call simulateTypingIn $inputor expect(remote_call).toHaveBeenCalled() it "can sort the data", -> names = callbacks.beforeSave.call(app.controller(), fixtures["names"]) names = callbacks.sorter.call(app, "e", names, "name") expect(names[0].name).toBe 'Ethan' it "can sort numeric data", -> numerics = callbacks.beforeSave.call(app.controller(), fixtures["numerics"]) numerics = callbacks.sorter.call(app, "1", numerics, "name") expect(numerics[0].name).toBe 13 it "don't sort the data without a query", -> names = callbacks.beforeSave.call(app.controller(), fixtures["names"]) names = callbacks.sorter.call(app, "", names, "name") expect(names[0]).toEqual({ name : 'Jacob' }) it "can eval temple", -> map = {name: "username", nick: "nick_name"} tpl = '
  • ${nick}
  • ' html = '
  • nick_name
  • ' result = callbacks.tplEval.call(app, tpl, map) expect(result).toBe(html) it "can evaluate template as a function", -> map = {name: "username", nick: "nick_name"} tpl = (map)-> '
  • '+map.nick+'
  • ' html = '
  • nick_name
  • ' result = callbacks.tplEval.call(app, tpl, map) expect(result).toBe(html) it "can highlight the query", -> html = '
  • Ethan
  • ' highlighted = callbacks.highlighter.call(app, html, "e") result = '
  • Ethan
  • ' expect(highlighted).toBe(result) it "can insert the text which be choosed", -> spyOn(callbacks, "beforeInsert").and.callThrough() triggerAtwhoAt $inputor expect(callbacks.beforeInsert).toHaveBeenCalled() it "can adjust offset before reposition", -> spyOn(callbacks, "beforeReposition").and.callThrough() triggerAtwhoAt $inputor expect(callbacks.beforeReposition).toHaveBeenCalled() At.js-1.5.4/spec/javascripts/events.spec.coffee000066400000000000000000000070211311725210300213640ustar00rootroot00000000000000 describe "events", -> $inputor = null app = null $ = jQuery beforeEach -> loadFixtures "inputors.html" $inputor = $("#inputor").atwho at: "@", data: fixtures["names"] app = getAppOf $inputor afterEach -> $inputor.atwho 'destroy' describe "inner", -> controller = null callbacks = null beforeEach -> controller = app.controller() callbacks = $.fn.atwho.default.callbacks simulateTypingIn $inputor it "trigger esc", -> esc_event = $.Event("keyup.atwhoInner", keyCode: KEY_CODE.ESC) $inputor.trigger(esc_event) expect(controller.view.visible()).toBe(false) it "trigger tab", -> spyOn(callbacks, "beforeInsert").and.callThrough() tab_event = $.Event("keydown.atwhoInner", keyCode: KEY_CODE.TAB) $inputor.trigger(tab_event) expect(controller.view.visible()).toBe(false) expect(callbacks.beforeInsert).toHaveBeenCalled() it "trigger enter", -> spyOn(callbacks, "beforeInsert").and.callThrough() enter_event = $.Event("keydown.atwhoInner", keyCode: KEY_CODE.ENTER) $inputor.trigger(enter_event) expect(callbacks.beforeInsert).toHaveBeenCalled() it "trigger up", -> spyOn(controller.view, "prev").and.callThrough() up_event = $.Event("keydown.atwhoInner", keyCode: KEY_CODE.UP) $inputor.trigger(up_event) expect(controller.view.prev).toHaveBeenCalled() it "trigger down", -> spyOn(controller.view, "next").and.callThrough() down_event = $.Event("keydown.atwhoInner", keyCode: KEY_CODE.DOWN) $inputor.trigger(down_event) expect(controller.view.next).toHaveBeenCalled() it "trigger up(ctrl + p)", -> spyOn(controller.view, "prev").and.callThrough() up_event = $.Event("keydown.atwhoInner", keyCode: KEY_CODE.P, ctrlKey: true) $inputor.trigger(up_event) expect(controller.view.prev).toHaveBeenCalled() it "trigger down(ctrl + n)", -> spyOn(controller.view, "next").and.callThrough() down_event = $.Event("keydown.atwhoInner", keyCode: KEY_CODE.N, ctrlKey: true) $inputor.trigger(down_event) expect(controller.view.next).toHaveBeenCalled() it "trigger p", -> spyOn(controller.view, "prev").and.callThrough() p_event = $.Event("keydown.atwhoInner", keyCode: KEY_CODE.P, ctrlKey: false) $inputor.trigger(p_event) expect(controller.view.prev).not.toHaveBeenCalled() it "trigger n", -> spyOn(controller.view, "prev").and.callThrough() n_event = $.Event("keydown.atwhoInner", keyCode: KEY_CODE.N, ctrlKey: false) $inputor.trigger(n_event) expect(controller.view.prev).not.toHaveBeenCalled() describe "atwho", -> it "trigger matched", -> matched_event = spyOnEvent($inputor, "matched.atwho") triggerAtwhoAt $inputor expect(matched_event).toHaveBeenTriggered() it "trigger inserted", -> choose_event = spyOnEvent($inputor, "inserted.atwho") triggerAtwhoAt $inputor expect(choose_event).toHaveBeenTriggered() it "trigger reposition", -> reposition_event = spyOnEvent($inputor, "reposition.atwho") triggerAtwhoAt $inputor expect(reposition_event).toHaveBeenTriggered() it "trigger a special matched for @ with alias", -> $inputor.atwho at: "@" alias: "at-memtions" event = spyOnEvent($inputor, "matched-at-memtions.atwho") triggerAtwhoAt $inputor expect(event).toHaveBeenTriggered() it "trigger beforeDestroy", -> destroy_event = spyOnEvent($inputor, "beforeDestroy.atwho") $inputor.atwho 'destroy' expect(destroy_event).toHaveBeenTriggered() At.js-1.5.4/spec/javascripts/fixtures/000077500000000000000000000000001311725210300176275ustar00rootroot00000000000000At.js-1.5.4/spec/javascripts/fixtures/inputors.html000066400000000000000000000013711311725210300224020ustar00rootroot00000000000000
    Stay Foolish, Stay Hungry. @J sss shfsdfssfasf sjfsl
    At.js-1.5.4/spec/javascripts/fixtures/json/000077500000000000000000000000001311725210300206005ustar00rootroot00000000000000At.js-1.5.4/spec/javascripts/fixtures/json/data.json000066400000000000000000000016711311725210300224110ustar00rootroot00000000000000{ "names" : [ "Jacob","Isabella","Ethan","Emma","Michael","Olivia", "Alexander","Sophia","William","Ava","Joshua","Emily", "Daniel","Madison","Jayden","lepture","Abigail","Noah", "Chloe","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","你好", "你你你", "高富帅", "Jérémy" ], "emojis": [ "six_pointed_star", "ski", "skull", "sleepy", "slot_machine", "smile", "smiley", "smirk", "smoking", "snake", "snowman", "sob", "soccer", "space_invader", "spades", "spaghetti", "sparkler", "sparkles", "speaker", "speedboat", "squirrel", "star", "star2", "stars", "station", "statue_of_liberty", "stew", "strawberry", "sunflower", "sunny", "sunrise", "sunrise_over_mountains", "surfer", "sushi", "suspect", "sweat", "sweat_drops", "swimmer", "syringe", "tada", "tangerine", "taurus", "taxi", "tea", "telephone", "tennis", "tent", "thumbsdown", "+1","-1" ], "numerics": [ 6, 7, 13, 5, 4, 9, 1, 15, 14, 3, 8, 12, 2, 11, 10 ] } At.js-1.5.4/spec/javascripts/iframe.spec.coffee000066400000000000000000000017251311725210300213300ustar00rootroot00000000000000describe "iframe editor", -> $inputor = null app = null $ = jQuery beforeEach -> loadFixtures "inputors.html" ifr = $('#iframeInput')[0] doc = ifr.contentDocument || iframe.contentWindow.document if (ifrBody = doc.body) is null # IE doc.write "" ifrBody = doc.body ifrBody.contentEditable = true ifrBody.id = 'ifrBody' ifrBody.innerHTML = 'Stay Foolish, Stay Hungry. @Jobs' $inputor = $(ifrBody) $inputor.atwho('setIframe', ifr) $inputor.atwho(at: "@", data: ['Jobs']) app = getAppOf $inputor afterEach -> $inputor.atwho 'destroy' it "can insert content", -> triggerAtwhoAt $inputor expect($inputor.text()).toContain('@Jobs') it "insert by click", -> simulateTypingIn $inputor app.controller().view.$el.find('ul').children().first().trigger('click') expect($inputor.text()).toContain('@Jobs') At.js-1.5.4/spec/javascripts/settings.spec.coffee000066400000000000000000000074611311725210300217300ustar00rootroot00000000000000describe "settings", -> $inputor = null app = null controller = null callbacks = null $ = jQuery beforeEach -> loadFixtures("inputors.html") $inputor = $("#inputor").atwho at: "@", data: fixtures["names"] app = getAppOf $inputor controller = app.controller() callbacks = $.fn.atwho.default.callbacks afterEach -> $inputor.atwho 'destroy' it "update common settings", -> func = () -> $.noop old = $.extend {}, $.fn.atwho.default.callbacks $.fn.atwho.default.callbacks.filter = func $.fn.atwho.default.limit = 8 $inputor = $("").atwho at: "@" controller = $inputor.data('atwho').setContextFor("@").controller() expect(controller.callbacks("filter")).toBe func expect(controller.getOpt("limit")).toBe 8 $.extend $.fn.atwho.default.callbacks, old it "setting empty at", -> $inputor = $("").atwho at: "" controller = $inputor.data('atwho').controller "" expect(controller).toBeDefined() it "update specific settings", -> $inputor.atwho at: "@", limit: 3 expect(controller.setting.limit).toBe(3) it "update callbacks", -> filter = jasmine.createSpy("custom filter") spyOn(callbacks, "filter") $inputor.atwho at: "@" callbacks: filter: filter triggerAtwhoAt $inputor expect(filter).toHaveBeenCalled() expect(callbacks.filter).not.toHaveBeenCalled() it "setting timeout", -> jasmine.clock().install() $inputor.atwho at: "@" displayTimeout: 500 simulateTypingIn $inputor $inputor.trigger "blur" view = controller.view.$el expect(view).not.toBeHidden() jasmine.clock().tick 503 expect(view).toBeHidden() jasmine.clock().uninstall() it "escape RegExp flag", -> $inputor = $('#inputor2').atwho at: "$" data: fixtures["names"] controller = $inputor.data('atwho').setContextFor("$").controller() simulateTypingIn $inputor, "$" expect(controller.view.visible()).toBe true it "can be trigger with no space", -> $inputor = $('#inputor3').atwho at: "@" data: fixtures["names"] startWithSpace: no controller = $inputor.data('atwho').setContextFor("@").controller() simulateTypingIn $inputor expect(controller.view.visible()).toBe true it 'highlight first', -> simulateTypingIn $inputor expect(controller.view.$el.find('ul li:first')).toHaveClass('cur') $inputor.atwho at: '@' highlightFirst: false simulateTypingIn $inputor expect(controller.view.$el.find('ul li:first')).not.toHaveClass('cur') it 'query out of maxLen', -> $inputor.atwho at: '@' maxLen: 0 simulateTypingIn $inputor expect(controller.query).toBe null it 'should not build query or run afterMatchFailed callback when out of minLen', -> $inputor = $('#editable').atwho at: '@' minLen: 2 callbacks: afterMatchFailed: (at, $el) -> $el.replaceWith('
    ') simulateTypingIn $inputor expect(controller.query).toBe null expect($('#failed-match').length).toBe 0 describe "`data` as url and load remote data", -> beforeEach -> jasmine.Ajax.install() controller = app.controller() controller.model.save null $inputor.atwho at: "@" data: "/atwho.json" afterEach -> jasmine.Ajax.uninstall() it "data should be empty at first", -> expect(controller.model.fetch().length).toBe 0 it "should load data after focus inputor", -> simulateTypingIn $inputor request = jasmine.Ajax.requests.mostRecent() response_data = [{"name":"Jacob"}, {"name":"Joshua"}, {"name":"Jayden"}] request.respondWith status: 200 responseText: JSON.stringify(response_data) expect(controller.model.fetch().length).toBe 3 At.js-1.5.4/spec/javascripts/view.spec.coffee000066400000000000000000000020601311725210300210300ustar00rootroot00000000000000 describe "views", -> $inputor = null app = null $ = jQuery beforeEach -> loadFixtures "inputors.html" $inputor = $ "#inputor" .atwho at: "@", data: fixtures["names"] app = getAppOf $inputor afterEach -> $inputor.atwho 'destroy' describe "issues", -> controller = null callbacks = null beforeEach -> controller = app.controller() callbacks = $.fn.atwho.default.callbacks simulateTypingIn $inputor it "selected no highlight(.cur); github issues#234", -> simulateTypingIn $inputor expect targetLi = controller.view.$el.find('ul li:last') .not.toHaveClass 'cur' spyOn controller.view, "choose" .and.callThrough() targetLi.trigger clickEvent = $.Event("click.atwho-view") expect targetLi .toHaveClass 'cur' it "only hides on scroll if scrollTop is changed (github issue #305)", -> simulateTypingIn $inputor expect(controller.view.visible()).toBe true $inputor.trigger 'scroll' expect(controller.view.visible()).toBe true At.js-1.5.4/spec/spec_helper.coffee000066400000000000000000000020131311725210300170630ustar00rootroot00000000000000$ = jQuery @KEY_CODE = DOWN: 40 UP: 38 ESC: 27 TAB: 9 ENTER: 13 CTRL: 17 P: 80 N: 78 @fixtures or= loadJSONFixtures("data.json")["data.json"] @triggerAtwhoAt = ($inputor) -> simulateTypingIn $inputor simulateChoose $inputor @simulateTypingIn = ($inputor, flag, pos=31) -> $inputor.data("atwho").setContextFor flag || "@" oDocument = $inputor[0].ownerDocument oWindow = oDocument.defaultView || oDocument.parentWindow if $inputor.attr('contentEditable') == 'true' && oWindow.getSelection $inputor.focus() sel = oWindow.getSelection() range = oDocument.createRange() range.setStart $inputor.contents().get(0), pos range.setEnd $inputor.contents().get(0), pos range.collapse false sel.removeAllRanges() sel.addRange(range) else $inputor.caret('pos', pos) $inputor.trigger("keyup") @simulateChoose = ($inputor) -> e = $.Event("keydown", keyCode: KEY_CODE.ENTER) $inputor.trigger(e) @getAppOf = ($inputor, at = "@") -> $inputor.data('atwho').setContextFor(at) At.js-1.5.4/specRunner.html000066400000000000000000000041361311725210300155110ustar00rootroot00000000000000 At.js-1.5.4/src/000077500000000000000000000000001311725210300132625ustar00rootroot00000000000000At.js-1.5.4/src/api.coffee000066400000000000000000000031541311725210300152070ustar00rootroot00000000000000Api = # load a flag's data # # @params at[String] the flag # @params data [Array] data to storage. load: (at, data) -> c.model.load data if c = this.controller(at) isSelecting: () -> !!this.controller()?.view.visible() hide: () -> this.controller()?.view.hide() reposition: () -> if c = this.controller() c.view.reposition(c.rect()) setIframe: (iframe, asRoot) -> this.setupRootElement(iframe, asRoot); null; run: -> this.dispatch() destroy: -> this.shutdown() @$inputor.data('atwho', null) $.fn.atwho = (method) -> _args = arguments result = null this.filter('textarea, input, [contenteditable=""], [contenteditable=true]').each -> if not app = ($this = $ this).data "atwho" $this.data 'atwho', (app = new App this) if typeof method is 'object' || !method app.reg method.at, method else if Api[method] and app result = Api[method].apply app, Array::slice.call(_args, 1) else $.error "Method #{method} does not exist on jQuery.atwho" if result? then result else this $.fn.atwho.default = at: undefined alias: undefined data: null displayTpl: "
  • ${name}
  • " insertTpl: "${atwho-at}${name}" headerTpl: null callbacks: DEFAULT_CALLBACKS functionOverrides: {} searchKey: "name" suffix: undefined hideWithoutSuffix: no startWithSpace: yes acceptSpaceBar: false highlightFirst: yes limit: 5 maxLen: 20 minLen: 0 displayTimeout: 300 delay: null spaceSelectsMatch: no tabSelectsMatch: yes editableAtwhoQueryAttrs: {} scrollDuration: 150 suspendOnComposing: true lookUpOnClick: true $.fn.atwho.debug = false At.js-1.5.4/src/app.coffee000066400000000000000000000112531311725210300152150ustar00rootroot00000000000000# At.js central contoller(searching, matching, evaluating and rendering.) class App # @param inputor [HTML DOM Object] `input` or `textarea` constructor: (inputor) -> @currentFlag = null @controllers = {} @aliasMaps = {} @$inputor = $(inputor) this.setupRootElement() this.listen() createContainer: (doc) -> @$el?.remove() $ doc.body .append @$el = $ "
    " setupRootElement: (iframe, asRoot=false) -> if iframe @window = iframe.contentWindow @document = iframe.contentDocument || @window.document @iframe = iframe else @document = @$inputor[0].ownerDocument @window = @document.defaultView || @document.parentWindow try @iframe = @window.frameElement catch error @iframe = null if $.fn.atwho.debug throw new Error """ iframe auto-discovery is failed. Please use `setIframe` to set the target iframe manually. #{error} """ this.createContainer if @iframeAsRoot = asRoot then @document else document controller: (at) -> if @aliasMaps[at] current = @controllers[@aliasMaps[at]] else for currentFlag, c of @controllers if currentFlag is at current = c break if current then current else @controllers[@currentFlag] setContextFor: (at) -> @currentFlag = at this # At.js can register multiple at char (flag) to every inputor such as "@" and ":" # Along with their own `settings` so that it works differently. # After register, we still can update their `settings` such as updating `data` # # @param flag [String] at char (flag) # @param settings [Hash] the settings reg: (flag, setting) -> controller = @controllers[flag] ||= if @$inputor.is '[contentEditable]' new EditableController this, flag else new TextareaController this, flag # TODO: it will produce rubbish alias map, reduse this. @aliasMaps[setting.alias] = flag if setting.alias controller.init setting this # binding jQuery events of `inputor`'s listen: -> @$inputor .on 'compositionstart', (e) => this.controller()?.view.hide() @isComposing = true null .on 'compositionend', (e) => @isComposing = false setTimeout((e) => @dispatch(e)) null .on 'keyup.atwhoInner', (e) => this.onKeyup(e) .on 'keydown.atwhoInner', (e) => this.onKeydown(e) .on 'blur.atwhoInner', (e) => if c = this.controller() c.expectedQueryCBId = null c.view.hide(e,c.getOpt("displayTimeout")) .on 'click.atwhoInner', (e) => this.dispatch e .on 'scroll.atwhoInner', do => # make returned handler handle the very first call properly lastScrollTop = @$inputor.scrollTop() (e) => currentScrollTop = e.target.scrollTop if lastScrollTop != currentScrollTop @controller()?.view.hide(e) lastScrollTop = currentScrollTop true # ensure we don't stop bubbling shutdown: -> for _, c of @controllers c.destroy() delete @controllers[_] @$inputor.off '.atwhoInner' @$el.remove() dispatch: (e) -> c.lookUp(e) for _, c of @controllers onKeyup: (e) -> switch e.keyCode when KEY_CODE.ESC e.preventDefault() this.controller()?.view.hide() when KEY_CODE.DOWN, KEY_CODE.UP, KEY_CODE.CTRL, KEY_CODE.ENTER $.noop() when KEY_CODE.P, KEY_CODE.N this.dispatch e if not e.ctrlKey else this.dispatch e # coffeescript will return everywhere!! return onKeydown: (e) -> # return if not (view = this.controller().view).visible() view = this.controller()?.view return if not (view and view.visible()) switch e.keyCode when KEY_CODE.ESC e.preventDefault() view.hide(e) when KEY_CODE.UP e.preventDefault() view.prev() when KEY_CODE.DOWN e.preventDefault() view.next() when KEY_CODE.P return if not e.ctrlKey e.preventDefault() view.prev() when KEY_CODE.N return if not e.ctrlKey e.preventDefault() view.next() when KEY_CODE.TAB, KEY_CODE.ENTER, KEY_CODE.SPACE return if not view.visible() return if not this.controller().getOpt('spaceSelectsMatch') and e.keyCode == KEY_CODE.SPACE return if not this.controller().getOpt('tabSelectsMatch') and e.keyCode == KEY_CODE.TAB if view.highlighted() e.preventDefault() view.choose(e) else view.hide(e) else $.noop() return At.js-1.5.4/src/controller.coffee000066400000000000000000000103471311725210300166230ustar00rootroot00000000000000class Controller uid: -> (Math.random().toString(16)+"000000000").substr(2,8) + (new Date().getTime()) constructor: (@app, @at) -> @$inputor = @app.$inputor @id = @$inputor[0].id || this.uid() @expectedQueryCBId = null @setting = null @query = null @pos = 0 @range = null if (@$el = $("#atwho-ground-#{@id}", @app.$el)).length == 0 @app.$el.append @$el = $("
    ") @model = new Model(this) @view = new View(this) init: (setting) -> @setting = $.extend {}, @setting || $.fn.atwho.default, setting @view.init() @model.reload @setting.data destroy: -> this.trigger 'beforeDestroy' @model.destroy() @view.destroy() @$el.remove() callDefault: (funcName, args...) -> try DEFAULT_CALLBACKS[funcName].apply this, args catch error $.error "#{error} Or maybe At.js doesn't have function #{funcName}" # Delegate custom `jQueryEvent` to the inputor # This function will add `atwho` as namespace to every jQuery event # and pass current context as the last param to it. # # @example # this.trigger "roll_n_rock", [1,2,3,4] # # $inputor.on "rool_n_rock", (e, one, two, three, four) -> # console.log one, two, three, four # # @param name [String] Event name # @param data [Array] data to callback trigger: (name, data=[]) -> data.push this alias = this.getOpt('alias') eventName = if alias then "#{name}-#{alias}.atwho" else "#{name}.atwho" @$inputor.trigger eventName, data # Get callback either in settings which was set by plugin user or in default callbacks list. # # @param funcName [String] callback's name # @return [Function] The callback. callbacks: (funcName)-> this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName] # Because different registered at chars have different settings. # so we should give their own for them. # # @param at [String] setting's at name # @param default_value [?] return this if nothing is returned from current settings. # @return [?] setting's value getOpt: (at, default_value) -> try @setting[at] catch e null insertContentFor: ($li) -> tpl = this.getOpt('insertTpl') data = $.extend {}, $li.data('item-data'), {'atwho-at': @at} this.callbacks("tplEval").call(this, tpl, data, "onInsert") # Render list view # # @param data [Array] The data renderView: (data) -> searchKey = this.getOpt("searchKey") data = this.callbacks("sorter").call(this, @query.text, data[0..1000] , searchKey) @view.render data[0...this.getOpt('limit')] @arrayToDefaultHash: (data) -> return data if not $.isArray data for item in data if $.isPlainObject item then item else name:item # Searching! lookUp: (e) -> return if e && e.type == 'click' && !@getOpt('lookUpOnClick') return if @getOpt('suspendOnComposing') and @app.isComposing query = @catchQuery e if not query @expectedQueryCBId = null return query @app.setContextFor @at if wait = this.getOpt('delay') @_delayLookUp query, wait else @_lookUp query query _delayLookUp: (query, wait) -> now = if Date.now then Date.now() else new Date().getTime() @previousCallTime ||= now remaining = wait - (now - @previousCallTime) if 0 < remaining < wait @previousCallTime = now @_stopDelayedCall() @delayedCallTimeout = setTimeout(=> @previousCallTime = 0 @delayedCallTimeout = null @_lookUp query , wait) else @_stopDelayedCall() @previousCallTime = 0 if @previousCallTime isnt now @_lookUp query _stopDelayedCall: -> if @delayedCallTimeout clearTimeout @delayedCallTimeout @delayedCallTimeout = null _generateQueryCBId: -> return {}; _lookUp: (query) -> _callback = (queryCBId, data) -> # ensure only the latest instance of this function perform actions if queryCBId isnt @expectedQueryCBId return if data and data.length > 0 this.renderView @constructor.arrayToDefaultHash data else @view.hide() @expectedQueryCBId = @_generateQueryCBId() @model.query query.text, $.proxy(_callback, this, @expectedQueryCBId) At.js-1.5.4/src/default.coffee000066400000000000000000000111531311725210300160600ustar00rootroot00000000000000KEY_CODE = ESC: 27 TAB: 9 ENTER: 13 CTRL: 17 A: 65 P: 80 N: 78 LEFT: 37 UP:38 RIGHT: 39 DOWN: 40 BACKSPACE: 8 SPACE: 32 # Functions set for handling and rendering the data. # Others developers can override these methods to tweak At.js such as matcher. # We can override them in `callbacks` settings. # # @mixin # # The context of these functions is `$.atwho.Controller` object and they are called in this sequences: # # [beforeSave, matcher, filter, remoteFilter, sorter, tplEvl, highlighter, beforeInsert, afterMatchFailed] # DEFAULT_CALLBACKS = # It would be called to restructure the data before At.js invokes `Model#save` to save data # By default, At.js will convert it to a Hash Array. # # @param data [Array] data to refacotor. # @return [Array] Data after refactor. beforeSave: (data) -> Controller.arrayToDefaultHash data # It would be called to match the `flag`. # It will match at start of line or after whitespace # # @param flag [String] current `flag` ("@", etc) # @param subtext [String] Text from start to current caret position. # @param should_startWithSpace [boolean] accept white space as beginning of match. # @param acceptSpaceBar [boolean] accept a space bar in the center of match, # so you can match a first and last name, for ex. # # @return [String | null] Matched result. matcher: (flag, subtext, should_startWithSpace, acceptSpaceBar) -> # escape RegExp flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") flag = '(?:^|\\s)' + flag if should_startWithSpace # À _a = decodeURI("%C3%80") # ÿ _y = decodeURI("%C3%BF") space = if acceptSpaceBar then "\ " else "" regexp = new RegExp "#{flag}([A-Za-z#{_a}-#{_y}0-9_#{space}\'\.\+\-]*)$|#{flag}([^\\x00-\\xff]*)$",'gi' match = regexp.exec subtext if match then match[2] || match[1] else null # --------------------- # Filter data by matched string. # # @param query [String] Matched string. # @param data [Array] data list # @param searchKey [String] at char for searching. # # @return [Array] result data. filter: (query, data, searchKey) -> # !!null #=> false; !!undefined #=> false; !!'' #=> false; _results = [] for item in data _results.push item if ~new String(item[searchKey]).toLowerCase().indexOf query.toLowerCase() _results # If a function is given, At.js will invoke it if local filter can not find any data # # @param params [String] matched query # @param callback [Function] callback to render page. remoteFilter: null # remoteFilter: (query, callback) -> # $.ajax url, # data: params # success: (data) -> # callback(data) # Sorter data of course. # # @param query [String] matched string # @param items [Array] data that was refactored # @param searchKey [String] at char to search # # @return [Array] sorted data sorter: (query, items, searchKey) -> return items unless query _results = [] for item in items item.atwho_order = new String(item[searchKey]).toLowerCase().indexOf query.toLowerCase() _results.push item if item.atwho_order > -1 _results.sort (a,b) -> a.atwho_order - b.atwho_order # Evaluate the template either as a string or as a function # this allows someone to pass in a set of data that needs a # different template for different data results # # @param tpl [function] the template function or string # @param map [Hash] Data map to eval. tplEval: (tpl, map) -> template = tpl try template = tpl(map) unless typeof tpl == 'string' template.replace /\$\{([^\}]*)\}/g, (tag, key, pos) -> map[key] catch error "" # Highlight the `matched query` string. # # @param li [String] HTML String after eval. # @param query [String] matched query. # # @return [String] highlighted string. highlighter: (li, query) -> return li if not query regexp = new RegExp(">\\s*([^\<]*?)(" + query.replace("+","\\+") + ")([^\<]*)\\s*<", 'ig') li.replace regexp, (str, $1, $2, $3) -> '> '+$1+'' + $2 + ''+$3+' <' # What to do before inserting item's value into inputor. # # @param value [String] content to insert # @param $li [jQuery Object] the chosen item # @param e [event Object] from the user selection (keyDown or click) beforeInsert: (value, $li, e) -> value # You can adjust the menu's offset here. # # @param offset [Hash] offset will be applied to menu # beforeReposition: (offset) -> # offset.left += 10 # offset.top += 10 # offset beforeReposition: (offset) -> offset afterMatchFailed: (at, el) -> At.js-1.5.4/src/editableController.coffee000066400000000000000000000147351311725210300202620ustar00rootroot00000000000000class EditableController extends Controller _getRange: -> sel = @app.window.getSelection() sel.getRangeAt(0) if sel.rangeCount > 0 _setRange: (position, node, range=@_getRange()) -> return unless range and node node = $(node)[0] if position == 'after' range.setEndAfter node range.setStartAfter node else range.setEndBefore node range.setStartBefore node range.collapse false @_clearRange range _clearRange: (range=@_getRange()) -> sel = @app.window.getSelection() #ctrl+a remove defaults using the flag if !@ctrl_a_pressed? sel.removeAllRanges() sel.addRange range _movingEvent: (e) -> e.type == 'click' or e.which in [KEY_CODE.RIGHT, KEY_CODE.LEFT, KEY_CODE.UP, KEY_CODE.DOWN] _unwrap: (node) -> node = $(node).unwrap().get 0 if (next = node.nextSibling) and next.nodeValue node.nodeValue += next.nodeValue $(next).remove() node catchQuery: (e) -> return unless range = @_getRange() return unless range.collapsed if e.which == KEY_CODE.ENTER ($query = $(range.startContainer).closest '.atwho-query') .contents().unwrap() $query.remove() if $query.is ':empty' ($query = $ ".atwho-query", @app.document) .text $query.text() .contents().last().unwrap() @_clearRange() return # absorb range # The range at the end of an element is not inside in firefox but not others browsers including IE. # To normolize them, we have to move the range inside the element while deleting content or moving caret right after .atwho-inserted if /firefox/i.test(navigator.userAgent) if $(range.startContainer).is @$inputor @_clearRange() return if e.which == KEY_CODE.BACKSPACE and range.startContainer.nodeType == document.ELEMENT_NODE \ and (offset = range.startOffset - 1) >= 0 _range = range.cloneRange() _range.setStart range.startContainer, offset if $(_range.cloneContents()).contents().last().is '.atwho-inserted' inserted = $(range.startContainer).contents().get(offset) @_setRange 'after', $(inserted).contents().last() else if e.which == KEY_CODE.LEFT and range.startContainer.nodeType == document.TEXT_NODE $inserted = $ range.startContainer.previousSibling if $inserted.is('.atwho-inserted') and range.startOffset == 0 @_setRange 'after', $inserted.contents().last() # modifying inserted element $(range.startContainer) .closest '.atwho-inserted' .addClass 'atwho-query' .siblings().removeClass 'atwho-query' if ($query = $ ".atwho-query", @app.document).length > 0 \ and $query.is(':empty') and $query.text().length == 0 $query.remove() if not @_movingEvent e $query.removeClass 'atwho-inserted' if $query.length > 0 switch e.which when KEY_CODE.LEFT @_setRange 'before', $query.get(0), range $query.removeClass 'atwho-query' return when KEY_CODE.RIGHT @_setRange 'after', $query.get(0).nextSibling, range $query.removeClass 'atwho-query' return # matching if $query.length > 0 and query_content = $query.attr('data-atwho-at-query') $query.empty().html(query_content).attr('data-atwho-at-query', null) @_setRange 'after', $query.get(0), range _range = range.cloneRange() _range.setStart range.startContainer, 0 matched = @callbacks("matcher").call(this, @at, _range.toString(), @getOpt('startWithSpace'), @getOpt("acceptSpaceBar")) isString = typeof matched is 'string' # wrapping query with .atwho-query if $query.length == 0 and isString \ and (index = range.startOffset - @at.length - matched.length) >= 0 range.setStart range.startContainer, index $query = $ '', @app.document .attr @getOpt "editableAtwhoQueryAttrs" .addClass 'atwho-query' range.surroundContents $query.get 0 lastNode = $query.contents().last().get(0) if lastNode if /firefox/i.test navigator.userAgent range.setStart lastNode, lastNode.length range.setEnd lastNode, lastNode.length @_clearRange range else @_setRange 'after', lastNode, range return if isString and matched.length < @getOpt('minLen', 0) # handle the matched result if isString and matched.length <= @getOpt('maxLen', 20) query = text: matched, el: $query @trigger "matched", [@at, query.text] @query = query else @view.hide() @query = el: $query if $query.text().indexOf(this.at) >= 0 if @_movingEvent(e) and $query.hasClass 'atwho-inserted' $query.removeClass('atwho-query') else if false != @callbacks('afterMatchFailed').call this, @at, $query @_setRange "after", @_unwrap $query.text($query.text()).contents().first() null # Get offset of current at char(`flag`) # # @return [Hash] the offset which look likes this: {top: y, left: x, bottom: bottom} rect: -> rect = @query.el.offset() # do not use {top: 0, left: 0} from jQuery when element is hidden # happens every other time the menu is displayed on click in contenteditable return unless rect and @query.el[0].getClientRects().length if @app.iframe and not @app.iframeAsRoot iframeOffset = ($iframe = $ @app.iframe).offset() rect.left += iframeOffset.left - @$inputor.scrollLeft() rect.top += iframeOffset.top - @$inputor.scrollTop() rect.bottom = rect.top + @query.el.height() rect # Insert value of `data-value` attribute of chosen item into inputor # # @param content [String] string to insert insert: (content, $li) -> @$inputor.focus() unless @$inputor.is ':focus' overrides = @getOpt 'functionOverrides' if overrides.insert return overrides.insert.call this, content, $li suffix = if (suffix = @getOpt 'suffix') == "" then suffix else suffix or "\u00A0" data = $li.data('item-data') @query.el .removeClass 'atwho-query' .addClass 'atwho-inserted' .html content .attr 'data-atwho-at-query', "" + data['atwho-at'] + @query.text .attr 'contenteditable', "false" if range = @_getRange() if @query.el.length range.setEndAfter @query.el[0] range.collapse false range.insertNode suffixNode = @app.document.createTextNode "" + suffix @_setRange 'after', suffixNode, range @$inputor.focus() unless @$inputor.is ':focus' @$inputor.change() At.js-1.5.4/src/jquery.atwho.css000066400000000000000000000024401311725210300164340ustar00rootroot00000000000000.atwho-view { position:absolute; top: 0; left: 0; display: none; margin-top: 18px; background: white; color: black; border: 1px solid #DDD; border-radius: 3px; box-shadow: 0 0 5px rgba(0,0,0,0.1); min-width: 120px; z-index: 11110 !important; } .atwho-view .atwho-header { padding: 5px; margin: 5px; cursor: pointer; border-bottom: solid 1px #eaeff1; color: #6f8092; font-size: 11px; font-weight: bold; } .atwho-view .atwho-header .small { color: #6f8092; float: right; padding-top: 2px; margin-right: -5px; font-size: 12px; font-weight: normal; } .atwho-view .atwho-header:hover { cursor: default; } .atwho-view .cur { background: #3366FF; color: white; } .atwho-view .cur small { color: white; } .atwho-view strong { color: #3366FF; } .atwho-view .cur strong { color: white; font:bold; } .atwho-view ul { /* width: 100px; */ list-style:none; padding:0; margin:auto; max-height: 200px; overflow-y: auto; } .atwho-view ul li { display: block; padding: 5px 10px; border-bottom: 1px solid #DDD; cursor: pointer; /* border-top: 1px solid #C8C8C8; */ } .atwho-view small { font-size: smaller; color: #777; font-weight: normal; } At.js-1.5.4/src/model.coffee000066400000000000000000000032771311725210300155440ustar00rootroot00000000000000# Class to process data class Model constructor: (@context) -> @at = @context.at # NOTE: bind data storage to inputor maybe App class can handle it. @storage = @context.$inputor destroy: -> @storage.data(@at, null) saved: -> this.fetch() > 0 # fetch data from storage by query. # will invoke `callback` to return data # # @param query [String] catched string for searching # @param callback [Function] for receiving data query: (query, callback) -> data = this.fetch() searchKey = @context.getOpt("searchKey") data = @context.callbacks('filter').call(@context, query, data, searchKey) || [] _remoteFilter = @context.callbacks('remoteFilter') if data.length > 0 or (!_remoteFilter and data.length == 0) callback data else _remoteFilter.call(@context, query, callback) # get or set current data which would be shown on the list view. # # @param data [Array] set data # @return [Array|undefined] current data that are showing on the list view. fetch: -> @storage.data(@at) || [] # save special flag's data to storage # # @param data [Array] data to save save: (data) -> @storage.data @at, @context.callbacks("beforeSave").call(@context, data || []) # load data. It wouldn't load for a second time if it has been loaded. # # @param data [Array] data to load load: (data) -> this._load(data) unless this.saved() or not data reload: (data) -> this._load(data) # load data from local or remote with callback # # @param data [Array|String] data to load. _load: (data) -> if typeof data is "string" $.ajax(data, dataType: "json").done (data) => this.save(data) else this.save data At.js-1.5.4/src/textareaController.coffee000066400000000000000000000037641311725210300203260ustar00rootroot00000000000000class TextareaController extends Controller # Catch query string behind the at char # # @return [Hash] Info of the query. Look likes this: {'text': "hello", 'headPos': 0, 'endPos': 0} catchQuery: -> content = @$inputor.val() caretPos = @$inputor.caret('pos', {iframe: @app.iframe}) subtext = content.slice(0, caretPos) query = this.callbacks("matcher").call(this, @at, subtext, this.getOpt('startWithSpace'), @getOpt("acceptSpaceBar")) isString = typeof query is 'string' return if isString and query.length < this.getOpt('minLen', 0) if isString and query.length <= this.getOpt('maxLen', 20) start = caretPos - query.length end = start + query.length @pos = start query = {'text': query, 'headPos': start, 'endPos': end} this.trigger "matched", [@at, query.text] else query = null @view.hide() @query = query # Get offset of current at char(`flag`) # # @return [Hash] the offset which look likes this: {top: y, left: x, bottom: bottom} rect: -> return if not c = @$inputor.caret('offset', @pos - 1, {iframe: @app.iframe}) if @app.iframe and not @app.iframeAsRoot iframeOffset = $(@app.iframe).offset() c.left += iframeOffset.left c.top += iframeOffset.top scaleBottom = if @app.document.selection then 0 else 2 {left: c.left, top: c.top, bottom: c.top + c.height + scaleBottom} # Insert value of `data-value` attribute of chosen item into inputor # # @param content [String] string to insert insert: (content, $li) -> $inputor = @$inputor source = $inputor.val() startStr = source.slice 0, Math.max(@query.headPos - @at.length, 0) suffix = if (suffix = @getOpt 'suffix') == "" then suffix else suffix or " " content += suffix text = "#{startStr}#{content}#{source.slice @query['endPos'] || 0}" $inputor.val text $inputor.caret('pos', startStr.length + content.length, {iframe: @app.iframe}) $inputor.focus() unless $inputor.is ':focus' $inputor.change() At.js-1.5.4/src/view.coffee000066400000000000000000000104021311725210300154020ustar00rootroot00000000000000# View class to control how At.js's view showing. # All classes share the same DOM view. class View # @param controller [Object] The Controller. constructor: (@context) -> @$el = $("
      ") @$elUl = @$el.children(); @timeoutID = null # create HTML DOM of list view if it does not exist @context.$el.append(@$el) this.bindEvent() init: -> id = @context.getOpt("alias") || @context.at.charCodeAt(0) header_tpl = this.context.getOpt("headerTpl") if (header_tpl && this.$el.children().length == 1) this.$el.prepend(header_tpl) @$el.attr('id': "at-view-#{id}") destroy: -> @$el.remove() bindEvent: -> $menu = @$el.find('ul') lastCoordX = 0 lastCoordY = 0 $menu.on 'mousemove.atwho-view','li', (e) => # If the mouse hasn't actually moved then exit. return if lastCoordX == e.clientX and lastCoordY == e.clientY lastCoordX = e.clientX lastCoordY = e.clientY $cur = $(e.currentTarget) return if $cur.hasClass('cur') $menu.find('.cur').removeClass 'cur' $cur.addClass 'cur' .on 'click.atwho-view', 'li', (e) => $menu.find('.cur').removeClass 'cur' $(e.currentTarget).addClass 'cur' this.choose(e) e.preventDefault() # Check if view is visible # # @return [Boolean] visible: -> $.expr.filters.visible(@$el[0]) highlighted: -> @$el.find(".cur").length > 0 choose: (e) -> if ($li = @$el.find ".cur").length content = @context.insertContentFor $li @context._stopDelayedCall() @context.insert @context.callbacks("beforeInsert").call(@context, content, $li, e), $li @context.trigger "inserted", [$li, e] this.hide(e) @stopShowing = yes if @context.getOpt("hideWithoutSuffix") reposition: (rect) -> _window = if @context.app.iframeAsRoot then @context.app.window else window if rect.bottom + @$el.height() - $(_window).scrollTop() > $(_window).height() rect.bottom = rect.top - @$el.height() if rect.left > overflowOffset = $(_window).width() - @$el.width() - 5 rect.left = overflowOffset offset = {left:rect.left, top:rect.bottom} @context.callbacks("beforeReposition")?.call(@context, offset) @$el.offset offset @context.trigger "reposition", [offset] next: -> cur = @$el.find('.cur').removeClass('cur') next = cur.next() next = @$el.find('li:first') if not next.length next.addClass 'cur' nextEl = next[0] offset = nextEl.offsetTop + nextEl.offsetHeight + (if nextEl.nextSibling then nextEl.nextSibling.offsetHeight else 0) @scrollTop Math.max(0, offset - this.$el.height()) prev: -> cur = @$el.find('.cur').removeClass('cur') prev = cur.prev() prev = @$el.find('li:last') if not prev.length prev.addClass 'cur' prevEl = prev[0] offset = prevEl.offsetTop + prevEl.offsetHeight + (if prevEl.nextSibling then prevEl.nextSibling.offsetHeight else 0) @scrollTop Math.max(0, offset - this.$el.height()) scrollTop: (scrollTop) -> scrollDuration = @context.getOpt('scrollDuration') if scrollDuration @$elUl.animate {scrollTop: scrollTop}, scrollDuration else @$elUl.scrollTop(scrollTop) show: -> if @stopShowing @stopShowing = false return if not this.visible() @$el.show() @$el.scrollTop 0 @context.trigger 'shown' this.reposition(rect) if rect = @context.rect() hide: (e, time) -> return if not this.visible() if isNaN(time) @$el.hide() @context.trigger 'hidden', [e] else callback = => this.hide() clearTimeout @timeoutID @timeoutID = setTimeout callback, time # render list view render: (list) -> if not ($.isArray(list) and list.length > 0) this.hide() return @$el.find('ul').empty() $ul = @$el.find('ul') tpl = @context.getOpt('displayTpl') for item in list item = $.extend {}, item, {'atwho-at': @context.at} li = @context.callbacks("tplEval").call(@context, tpl, item, "onDisplay") $li = $ @context.callbacks("highlighter").call(@context, li, @context.query.text) $li.data("item-data", item) $ul.append $li this.show() $ul.find("li:first").addClass "cur" if @context.getOpt('highlightFirst') At.js-1.5.4/umd.template.js000066400000000000000000000010311311725210300154230ustar00rootroot00000000000000(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module unless amdModuleId is set define(["jquery"], function (a0) { return (factory(a0)); }); } else if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(require("jquery")); } else { factory(jQuery); } }(this, function ($) { <%= contents %> }));