зеркало из https://github.com/nextcloud/spreed.git
Add At.js and dependencies
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Родитель
63831a4455
Коммит
790bddd06d
|
@ -20,6 +20,7 @@
|
|||
"backbone": "1.2.3",
|
||||
"backbone.marionette": "3.0.0",
|
||||
"jquery": "^2.0",
|
||||
"jshashes": "^1.0"
|
||||
"jshashes": "^1.0",
|
||||
"ichord/At.js": "^1.4.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"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"
|
||||
],
|
||||
"homepage": "https://github.com/ichord/At.js",
|
||||
"_release": "1.5.4",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v1.5.4",
|
||||
"commit": "801c87dc804e37f134def2055b80cbc81ac98652"
|
||||
},
|
||||
"_source": "https://github.com/ichord/At.js.git",
|
||||
"_target": "^1.4.1",
|
||||
"_originalSource": "ichord/At.js=ichord/At.js"
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
### 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 `"<li>${name}</li>"`
|
||||
* 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: `<span class=".atwho-inserted"/>`
|
||||
Querying content are wrapped in a span: `<span class=".atwho-query"/>`
|
||||
|
||||
* 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.
|
|
@ -0,0 +1,37 @@
|
|||
## 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.
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (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.
|
|
@ -0,0 +1,67 @@
|
|||
**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
|
||||
<link href="css/jquery.atwho.css" rel="stylesheet">
|
||||
<script src="http://code.jquery.com/jquery.js"></script>
|
||||
<script src="js/jquery.caret.js"></script>
|
||||
<script src="js/jquery.atwho.js"></script>
|
||||
```
|
||||
|
||||
```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)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
.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;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
.atwho-view{position:absolute;top:0;left:0;display:none;margin-top:18px;background:#fff;color:#000;border:1px solid #DDD;border-radius:3px;box-shadow:0 0 5px rgba(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:700}.atwho-view .atwho-header .small{color:#6f8092;float:right;padding-top:2px;margin-right:-5px;font-size:12px;font-weight:400}.atwho-view .atwho-header:hover{cursor:default}.atwho-view .cur{background:#36F;color:#fff}.atwho-view .cur small{color:#fff}.atwho-view strong{color:#36F}.atwho-view .cur strong{color:#fff;font:700}.atwho-view ul{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}.atwho-view small{font-size:smaller;color:#777;font-weight:400}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Data Iframe</title>
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="http://ichord.github.io/Caret.js/src/jquery.caret.js"></script>
|
||||
<script type="text/javascript" src="../../dist/js/jquery.atwho.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
var names = ["Jacob","Isabella","Ethan","Emma","Michael","Olivia","Alexander","Sophia","William","Ava","Joshua","Emily","Daniel","Madison","Jayden","Abigail","Noah","Chloe","你好","你你你"];
|
||||
|
||||
var names = $.map(names,function(value,i) {
|
||||
return {'id':i,'name':value,'email':value+"@email.com"};
|
||||
});
|
||||
|
||||
viewFrame = parent.frames.viewFrame
|
||||
var at_config = {
|
||||
at: "@",
|
||||
data: names,
|
||||
displayTpl: "<li>${name} <small>${email}</small></li>"
|
||||
}
|
||||
$(viewFrame.document.body)
|
||||
.atwho('setIframe', viewFrame.frameElement, true)
|
||||
.atwho(at_config);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,90 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="x-ua-compatible" content="IE=Edge"/>
|
||||
<title>At.js</title>
|
||||
<style type="text/css">
|
||||
html, 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:70px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
header h1 {
|
||||
text-align: center;
|
||||
font-size: 75px;
|
||||
}
|
||||
h1 i {
|
||||
color: rgb(182, 180, 180);
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.inputor {
|
||||
height: 260px;
|
||||
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;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container wrapper">
|
||||
<header>
|
||||
<h1>At<i>.js</i></h1>
|
||||
</header>
|
||||
<div id="main">
|
||||
<h2>Cross-Document</h2>
|
||||
<iframe name="dataFrame" src="dataFrame.html" style="display:none;"></iframe>
|
||||
<iframe name="viewFrame" class="inputor" src="viewFrame.html"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>View Iframe</title>
|
||||
<link rel="stylesheet" href="../../dist/css/jquery.atwho.css" />
|
||||
</head>
|
||||
<body contenteditable=true style="height: 100%;">
|
||||
<p>hello!</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="x-ua-compatible" content="IE=Edge"/>
|
||||
<title>At.js</title>
|
||||
<link rel="stylesheet" href="../dist/css/jquery.atwho.css" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="http://ichord.github.io/Caret.js/src/jquery.caret.js"></script>
|
||||
<!-- // <script type="text/javascript" src="../bower_components/jquery/dist/jquery.js"></script> -->
|
||||
<!-- // <script type="text/javascript" src="../bower_components/Caret.js/dist/jquery.caret.js"></script> -->
|
||||
<script type="text/javascript" src="../dist/js/jquery.atwho.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
var jeremy = decodeURI("J%C3%A9r%C3%A9my") // Jérémy
|
||||
var tags = ["Jacob","Isabella","Ethan","Emma","Michael","Olivia","Alexander","Sophia","William","Ava","Joshua","Emily","Daniel","Madison","Jayden","Abigail","Noah","Chloe","你好","你你你", jeremy];
|
||||
$('#editable').atwho({
|
||||
at: "#",
|
||||
data: tags,
|
||||
limit: 200,
|
||||
callbacks: {
|
||||
afterMatchFailed: function(at, el) {
|
||||
// 32 is spacebar
|
||||
if (at == '#') {
|
||||
tags.push(el.text().trim().slice(1));
|
||||
this.model.save(tags);
|
||||
this.insert(el.text().trim());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style type="text/css">
|
||||
/*override atwho's style*/
|
||||
.atwho-inserted {
|
||||
color: #4183C4;
|
||||
}
|
||||
.atwho-query {
|
||||
color: #4183C4;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container wrapper">
|
||||
<header>
|
||||
<h3>Type `#` to autocomplete tags</h3>
|
||||
</header>
|
||||
<div id="main">
|
||||
<div id="editable" class="inputor" contentEditable="true"></div>
|
||||
<footer>
|
||||
<h2>
|
||||
-> <a class="github" href="https://github.com/ichord/At.js">Fork me on GitHub!</a>
|
||||
</h2>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="x-ua-compatible" content="IE=Edge"/>
|
||||
<title>At.js</title>
|
||||
<link rel="stylesheet" href="../dist/css/jquery.atwho.css" />
|
||||
<link rel="stylesheet" type="text/css" href="http://dfimg.com/medium-editor/dist/css/medium-editor.css">
|
||||
<link rel="stylesheet" type="text/css" href="http://dfimg.com/medium-editor/dist/css/themes/default.css">
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<!-- // <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> -->
|
||||
<!-- // <script type="text/javascript" src="http://ichord.github.io/Caret.js/src/jquery.caret.js"></script> -->
|
||||
<script type="text/javascript" src="../bower_components/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="../bower_components/Caret.js/dist/jquery.caret.js"></script>
|
||||
<script type="text/javascript" src="../dist/js/jquery.atwho.js"></script>
|
||||
<script src="http://dfimg.com/medium-editor/dist/js/medium-editor.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
var jeremy = decodeURI("J%C3%A9r%C3%A9my") // Jérémy
|
||||
var names = ["Jacob","Isabella","Ethan","Emma","Michael","Olivia","Alexander","Sophia","William","Ava","Joshua","Emily","Daniel","Madison","Jayden","Abigail","Noah","Chloe","你好","你你你", jeremy];
|
||||
var editor = new MediumEditor('#editor');
|
||||
$('#editor').atwho({at: "@", data: names});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container wrapper">
|
||||
<header>
|
||||
<h3>Example for medium-editor</h3>
|
||||
</header>
|
||||
<div id="main">
|
||||
<div id="editor" contentEditable>Easy! You should check out MoxieManager!</div>
|
||||
<footer>
|
||||
<h2>
|
||||
-> <a class="github" href="https://github.com/ichord/At.js">Fork me on GitHub!</a>
|
||||
</h2>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,57 @@
|
|||
html, 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;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="x-ua-compatible" content="IE=Edge"/>
|
||||
<title>At.js</title>
|
||||
<link rel="stylesheet" href="../dist/css/jquery.atwho.css" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<!-- // <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> -->
|
||||
<!-- // <script type="text/javascript" src="http://ichord.github.io/Caret.js/src/jquery.caret.js"></script> -->
|
||||
<script type="text/javascript" src="../bower_components/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="../bower_components/Caret.js/dist/jquery.caret.js"></script>
|
||||
<script type="text/javascript" src="../dist/js/jquery.atwho.js"></script>
|
||||
<script src="http://tinymce.cachefly.net/4.1/tinymce.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
var jeremy = decodeURI("J%C3%A9r%C3%A9my") // Jérémy
|
||||
var names = ["Jacob","Isabella","Ethan","Emma","Michael","Olivia","Alexander","Sophia","William","Ava","Joshua","Emily","Daniel","Madison","Jayden","Abigail","Noah","Chloe","你好","你你你", jeremy];
|
||||
tinymce.init({
|
||||
selector: "#editor",
|
||||
init_instance_callback: function(editor) {
|
||||
$(editor.contentDocument.activeElement).atwho({at: "@", data: names});
|
||||
},
|
||||
setup: function(editor) {
|
||||
editor.on('keydown', function(e) {
|
||||
if(e.keyCode == 13 && $(editor.contentDocument.activeElement).atwho('isSelecting'))
|
||||
return false
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container wrapper">
|
||||
<header>
|
||||
<h3>Example for tinyMCE editor</h3>
|
||||
</header>
|
||||
<div id="main">
|
||||
<textarea id="editor">Easy! You should check out MoxieManager!</textarea>
|
||||
<footer>
|
||||
<h2>
|
||||
-> <a class="github" href="https://github.com/ichord/At.js">Fork me on GitHub!</a>
|
||||
</h2>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<link rel="stylesheet" href="http://ueditor.baidu.com/ueditor/themes/default/css/ueditor.css" />
|
||||
<link rel="stylesheet" href="../dist/css/jquery.atwho.css" />
|
||||
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
|
||||
<script type="text/javascript" src="http://ichord.github.io/Caret.js/src/jquery.caret.js"></script>
|
||||
<script type="text/javascript" src="../dist/js/jquery.atwho.js"></script>
|
||||
|
||||
<title>ueditor</title>
|
||||
</head>
|
||||
<body>
|
||||
<script id="container" name="content" type="text/plain">
|
||||
test
|
||||
</script>
|
||||
<script src="http://ueditor.baidu.com/ueditor/ueditor.config.js"></script>
|
||||
<script src="http://ueditor.baidu.com/ueditor/ueditor.all.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var ue = UE.getEditor('container');
|
||||
$(function(){
|
||||
var at_config = {
|
||||
at: "@",
|
||||
data:['Peter', 'Tom', 'Anne', 'zhangsan', 'lisi', 'wangwu', 'laoliu', 'libai', 'dupu', 'xiaozhou'],
|
||||
limit: 20
|
||||
}
|
||||
|
||||
var ue = UE.getEditor('container',{
|
||||
contextMenu:[],
|
||||
//focus时自动清空初始化时的内容
|
||||
autoClearinitialContent:true,
|
||||
//关闭字数统计
|
||||
wordCount:false,
|
||||
//关闭elementPath
|
||||
elementPathEnabled:false,
|
||||
});
|
||||
ue.addListener('ready', function(editor){
|
||||
$(this.document.body).atwho(at_config);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,103 @@
|
|||
var 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']);
|
|
@ -0,0 +1,205 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="x-ua-compatible" content="IE=Edge"/>
|
||||
<title>At.js</title>
|
||||
<link rel="stylesheet" href="dist/css/jquery.atwho.css" />
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
|
||||
<script type="text/javascript" src="https://ichord.github.io/Caret.js/src/jquery.caret.js"></script>
|
||||
<!-- <script type="text/javascript" src="bower_components/jquery/dist/jquery.js"></script> -->
|
||||
<!-- <script type="text/javascript" src="bower_components/Caret.js/dist/jquery.caret.js"></script> -->
|
||||
<script type="text/javascript" src="dist/js/jquery.atwho.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$.fn.atwho.debug = true
|
||||
var emojis = [
|
||||
"smile", "iphone", "girl", "smiley", "heart", "kiss", "copyright", "coffee",
|
||||
"a", "ab", "airplane", "alien", "ambulance", "angel", "anger", "angry",
|
||||
"arrow_forward", "arrow_left", "arrow_lower_left", "arrow_lower_right",
|
||||
"arrow_right", "arrow_up", "arrow_upper_left", "arrow_upper_right",
|
||||
"art", "astonished", "atm", "b", "baby", "baby_chick", "baby_symbol",
|
||||
"balloon", "bamboo", "bank", "barber", "baseball", "basketball", "bath",
|
||||
"bear", "beer", "beers", "beginner", "bell", "bento", "bike", "bikini",
|
||||
"bird", "birthday", "black_square", "blue_car", "blue_heart", "blush",
|
||||
"boar", "boat", "bomb", "book", "boot", "bouquet", "bow", "bowtie",
|
||||
"boy", "bread", "briefcase", "broken_heart", "bug", "bulb",
|
||||
"person_with_blond_hair", "phone", "pig", "pill", "pisces", "plus1",
|
||||
"point_down", "point_left", "point_right", "point_up", "point_up_2",
|
||||
"police_car", "poop", "post_office", "postbox", "pray", "princess",
|
||||
"punch", "purple_heart", "question", "rabbit", "racehorse", "radio",
|
||||
"up", "us", "v", "vhs", "vibration_mode", "virgo", "vs", "walking",
|
||||
"warning", "watermelon", "wave", "wc", "wedding", "whale", "wheelchair",
|
||||
"white_square", "wind_chime", "wink", "wink2", "wolf", "woman",
|
||||
"womans_hat", "womens", "x", "yellow_heart", "zap", "zzz", "+1",
|
||||
"-1"
|
||||
]
|
||||
var jeremy = decodeURI("J%C3%A9r%C3%A9my") // Jérémy
|
||||
var names = ["Jacob","Isabella","Ethan","Emma","Michael","Olivia","Alexander","Sophia","William","Ava","Joshua","Emily","Daniel","Madison","Jayden","Abigail","Noah","Chloe","你好","你你你", jeremy, "가"];
|
||||
|
||||
var names = $.map(names,function(value,i) {
|
||||
return {'id':i,'name':value,'email':value+"@email.com"};
|
||||
});
|
||||
var emojis = $.map(emojis, function(value, i) {return {key: value, name:value}});
|
||||
|
||||
var at_config = {
|
||||
at: "@",
|
||||
data: names,
|
||||
headerTpl: '<div class="atwho-header">Member List<small>↑ ↓ </small></div>',
|
||||
insertTpl: '${name}',
|
||||
displayTpl: "<li>${name} <small>${email}</small></li>",
|
||||
limit: 200
|
||||
}
|
||||
var emoji_config = {
|
||||
at: ":",
|
||||
data: emojis,
|
||||
displayTpl: "<li>${name} <img src='https://assets-cdn.github.com/images/icons/emoji/${key}.png' height='20' width='20' /></li>",
|
||||
insertTpl: ':${key}:',
|
||||
delay: 400
|
||||
}
|
||||
$inputor = $('#inputor').atwho(at_config).atwho(emoji_config);
|
||||
$inputor.caret('pos', 47);
|
||||
$inputor.focus().atwho('run');
|
||||
|
||||
emoji_config.insertTpl = "<img src='https://assets-cdn.github.com/images/icons/emoji/${name}.png' height='20' width='20' />"
|
||||
$('#editable').atwho(at_config).atwho(emoji_config);
|
||||
|
||||
ifr = $('#iframe1')[0]
|
||||
doc = ifr.contentDocument || iframe.contentWindow.document
|
||||
if ((ifrBody = doc.body) == null) {
|
||||
// For IE
|
||||
doc.write("<body></body>")
|
||||
ifrBody = doc.body
|
||||
}
|
||||
ifrBody.contentEditable = true
|
||||
ifrBody.id = 'ifrBody'
|
||||
ifrBody.innerHTML = 'For <strong>WYSIWYG</strong> which using <strong>iframe</strong> such as <strong>ckeditor</strong>'
|
||||
$(ifrBody).atwho('setIframe', ifr).atwho(at_config)
|
||||
});
|
||||
|
||||
</script>
|
||||
<!--link href='http://fonts.googleapis.com/css?family=Dosis:400,700|Bubblegum+Sans|Overlock:400,900|PT+Sans:400,700|PT+Sans+Narrow:400,700|Magra|Asap:400,700|Share:400,700&subset=latin,latin-ext' rel='stylesheet' type='text/css' -->
|
||||
<link href='http://fonts.googleapis.com/css?family=PT+Sans:400,700' rel='stylesheet' type='text/css'>
|
||||
|
||||
<style type="text/css">
|
||||
html, 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:70px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
header h1 {
|
||||
text-align: center;
|
||||
font-size: 75px;
|
||||
}
|
||||
h1 i {
|
||||
color: rgb(182, 180, 180);
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
ul.doc {
|
||||
list-style:none;
|
||||
}
|
||||
ul.doc li {
|
||||
display:inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.github {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container wrapper">
|
||||
<!-- <a id="github" href="https://github.com/ichord/At.js" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0; z-index:999" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a> -->
|
||||
|
||||
<header>
|
||||
<h1>At<i>.js</i></h1>
|
||||
</header>
|
||||
<div id="main">
|
||||
<div>
|
||||
<textarea id="inputor" class="inputor">At.js, a github-like autocomplete library :s</textarea>
|
||||
</div>
|
||||
|
||||
<div id="editable" class="inputor" contentEditable="true">
|
||||
<p>
|
||||
<b>And!!</b> it support <b style="font-size: 20px">ContentEditable</b> mode too!!
|
||||
<img src="https://assets-cdn.github.com/images/icons/emoji/smile.png" height="20" width="20">
|
||||
<img src="https://assets-cdn.github.com/images/icons/emoji/smiley.png" height="20" width="20">
|
||||
<img src="https://assets-cdn.github.com/images/icons/emoji/coffee.png" height="20" width="20">
|
||||
</p>
|
||||
<p>
|
||||
<b>Try here now!</b><img src="https://assets-cdn.github.com/images/icons/emoji/point_right.png" height="20" width="20">
|
||||
<b>:h</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="inputor" style="overflow: hidden">
|
||||
<iframe src="" id="iframe1" style="width: 100%; height: 100%; border: 0px;"></iframe>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<h2>
|
||||
-> <a class="github" href="https://github.com/ichord/At.js">Fork me on GitHub!</a>
|
||||
</h2>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="node_modules/gulp-jasmine-phantom/vendor/jasmine-2.0/jasmine.css" />
|
||||
<link rel="stylesheet" type="text/css" href="dist/css/jquery.atwho.css" />
|
||||
<script type="text/javascript" src="node_modules/gulp-jasmine-phantom/vendor/jasmine-2.0/jasmine.js"></script>
|
||||
<script type="text/javascript" src="node_modules/gulp-jasmine-phantom/vendor/jasmine-2.0/jasmine-html.js"></script>
|
||||
<script type="text/javascript" src="node_modules/gulp-jasmine-phantom/vendor/jasmine-2.0/console.js"></script>
|
||||
<script type="text/javascript" src="node_modules/gulp-jasmine-phantom/vendor/jasmine-2.0/boot.js"></script>
|
||||
<script type="text/javascript" src="bower_components/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="bower_components/Caret.js/dist/jquery.caret.js"></script>
|
||||
<script type="text/javascript" src="dist/js/jquery.atwho.js"></script>
|
||||
<script type="text/javascript" src="node_modules/jasmine-jquery/lib/jasmine-jquery.js"></script>
|
||||
<script type="text/javascript" src="node_modules/jasmine-ajax/lib/mock-ajax.js"></script>
|
||||
<script type="text/javascript" src="spec/helpers/noConflict.js"></script>
|
||||
<script type="text/javascript" src="spec/build/spec_helper.js"></script>
|
||||
<script type="text/javascript" src="spec/build/javascripts/apis.spec.js"></script>
|
||||
<script type="text/javascript" src="spec/build/javascripts/content_editable.spec.js"></script>
|
||||
<script type="text/javascript" src="spec/build/javascripts/custom_callbacks.spec.js"></script>
|
||||
<script type="text/javascript" src="spec/build/javascripts/default_callbacks.spec.js"></script>
|
||||
<script type="text/javascript" src="spec/build/javascripts/events.spec.js"></script>
|
||||
<script type="text/javascript" src="spec/build/javascripts/iframe.spec.js"></script>
|
||||
<script type="text/javascript" src="spec/build/javascripts/settings.spec.js"></script>
|
||||
<script type="text/javascript" src="spec/build/javascripts/view.spec.js"></script>
|
||||
|
||||
<script type"text/javascript" src="node_modules/gulp-jasmine-phantom/lib/specRunner.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,59 @@
|
|||
Api =
|
||||
# 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: "<li>${name}</li>"
|
||||
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
|
|
@ -0,0 +1,158 @@
|
|||
# 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 = $ "<div class='atwho-container'></div>"
|
||||
|
||||
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
|
|
@ -0,0 +1,142 @@
|
|||
class 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 = $("<div id='atwho-ground-#{@id}'></div>")
|
||||
|
||||
@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)
|
|
@ -0,0 +1,147 @@
|
|||
KEY_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+'<strong>' + $2 + '</strong>'+$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) ->
|
|
@ -0,0 +1,174 @@
|
|||
class 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 = $ '<span/>', @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()
|
|
@ -0,0 +1,72 @@
|
|||
.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;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
# 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
|
|
@ -0,0 +1,51 @@
|
|||
class 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()
|
|
@ -0,0 +1,136 @@
|
|||
# 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 = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>")
|
||||
@$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')
|
|
@ -0,0 +1,17 @@
|
|||
(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 %>
|
||||
}));
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "Caret.js",
|
||||
"version": "0.2.2",
|
||||
"main": "src/jquery.caret.js",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"spec",
|
||||
"index.html"
|
||||
],
|
||||
"dependencies": {
|
||||
"jquery": ">=1.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jasmine-jquery": "~1.5.8"
|
||||
},
|
||||
"homepage": "https://github.com/ichord/Caret.js",
|
||||
"_release": "0.2.2",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v0.2.2",
|
||||
"commit": "b435c7049f1bce1c4db10526d956e24e8b484a52"
|
||||
},
|
||||
"_source": "https://github.com/ichord/Caret.js.git",
|
||||
"_target": "~0.2.2",
|
||||
"_originalSource": "Caret.js"
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
### v0.2.1
|
||||
|
||||
* f66a1eb - can get offset at the benginning of a line
|
||||
* 4885ddd - fix wrong position of textarea
|
||||
|
||||
### v0.2.0
|
||||
|
||||
* 12119d2 - calculating in iframe's coordinate
|
||||
* 959436d - implement `position` api for contentEditable
|
||||
* d051ffc - fix html escaping while mirroring caret
|
||||
|
||||
### v0.1.0
|
||||
|
||||
* b1f8f53 - fix Mirror div does not reset its CSS
|
||||
* e88e40e - fix Bad positioning in long words
|
||||
* 37d4c5e - disable auto decovery iframe
|
||||
|
||||
### v0.0.7
|
||||
|
||||
* cf94271 - Added suport for .caret(pos, 0) - Nicolas Donna
|
||||
* 2de2b0f - Fixed error when checking the pos arg when setting the position - Nicolas Donna
|
||||
* 34ac7fa - catch error thrown in cross-domain iframe - jiyinyiyong
|
||||
|
||||
* 01f1fa1 - add minified file in dist.
|
||||
|
||||
### v0.0.6
|
||||
|
||||
* 287b5d8 working in iframe
|
||||
|
||||
### v0.0.5
|
||||
|
||||
* aef0aa4 fix IE input position error
|
||||
* 4a4f7f7 fix contenteditable null value bug
|
||||
|
||||
### v0.0.4
|
||||
|
||||
* fix scrolling problem
|
||||
|
||||
### v0.0.2
|
||||
|
||||
* support `contentEditable` mode
|
||||
* fix ie bugs, and support IE > 6 to all mode
|
||||
|
||||
### 2013-08-07
|
||||
|
||||
* fix bug: error position at beginning of textarea
|
||||
* Bower
|
||||
* jasmine test
|
||||
|
||||
### 2013-03-31
|
||||
|
||||
* support IE browsers.
|
|
@ -0,0 +1,79 @@
|
|||
module.exports = (grunt) ->
|
||||
grunt.initConfig
|
||||
pkg: grunt.file.readJSON 'package.json'
|
||||
bower_path: 'bower_components'
|
||||
|
||||
jasmine:
|
||||
src: 'src/*.js'
|
||||
options:
|
||||
vendor: [
|
||||
'<%= bower_path %>/jquery/dist/jquery.min.js',
|
||||
'<%= bower_path %>/jasmine-jquery/lib/jasmine-jquery.js'
|
||||
]
|
||||
specs: 'spec/javascripts/*.js'
|
||||
# keepRunner: true
|
||||
|
||||
uglify:
|
||||
options:
|
||||
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
|
||||
build:
|
||||
files:
|
||||
'dist/<%= pkg.name %>.min.js': ['src/<%= pkg.name %>.js']
|
||||
|
||||
coffee:
|
||||
withMaps:
|
||||
options:
|
||||
bare: true
|
||||
sourceMap: true
|
||||
files:
|
||||
'src/<%= pkg.name %>.js': 'src/<%= pkg.name %>.coffee'
|
||||
withoutMaps:
|
||||
options:
|
||||
bare: true
|
||||
sourceMap: false
|
||||
files:
|
||||
'dist/<%= pkg.name %>.js': 'src/<%= pkg.name %>.coffee'
|
||||
|
||||
watch:
|
||||
scripts:
|
||||
files: ['src/*.coffee']
|
||||
tasks: ['coffee', 'umd']
|
||||
|
||||
umd:
|
||||
options:
|
||||
template: 'umd'
|
||||
deps:
|
||||
'default': ['$']
|
||||
amd: ['jquery']
|
||||
cjs: ['jquery']
|
||||
global:
|
||||
items: ['jQuery']
|
||||
prefix: ''
|
||||
src:
|
||||
src: 'src/<%= pkg.name %>.js'
|
||||
dist:
|
||||
src: 'dist/<%= pkg.name %>.js'
|
||||
|
||||
|
||||
'json-replace':
|
||||
options:
|
||||
space: " ",
|
||||
replace:
|
||||
version: "<%= pkg.version %>"
|
||||
'update-version':
|
||||
files:[{
|
||||
'bower.json': 'bower.json',
|
||||
'component.json': 'component.json'
|
||||
}]
|
||||
|
||||
grunt.loadNpmTasks 'grunt-contrib-coffee'
|
||||
grunt.loadNpmTasks 'grunt-contrib-uglify'
|
||||
grunt.loadNpmTasks 'grunt-contrib-jasmine'
|
||||
grunt.loadNpmTasks 'grunt-json-replace'
|
||||
grunt.loadNpmTasks 'grunt-contrib-watch'
|
||||
grunt.loadNpmTasks 'grunt-umd'
|
||||
|
||||
grunt.registerTask 'update-version', 'json-replace'
|
||||
|
||||
grunt.registerTask 'default', ['coffee', 'umd', 'jasmine','update-version', 'uglify', 'watch']
|
||||
grunt.registerTask 'test', ['coffee', 'umd', 'jasmine']
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (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.
|
|
@ -0,0 +1,54 @@
|
|||
Caret.js
|
||||
========
|
||||
|
||||
Get caret postion or offset from inputor
|
||||
|
||||
This is the core function that working in [At.js](http://ichord.github.com/At.js).
|
||||
Now, It just become an simple jquery plugin so that everybody can use it.
|
||||
And, of course, **At.js** is using this plugin too.
|
||||
|
||||
* support iframe context
|
||||
|
||||
Live Demo
|
||||
=========
|
||||
|
||||
http://ichord.github.com/Caret.js/
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
```javascript
|
||||
|
||||
// Get caret position
|
||||
$('#inputor').caret('position'); // => {left: 15, top: 30, height: 20}
|
||||
|
||||
// Get caret offset
|
||||
$('#inputor').caret('offset'); // => {left: 300, top: 400, height: 20}
|
||||
|
||||
var fixPos = 20
|
||||
// Get position of the 20th char in the inputor.
|
||||
// not working in `contentEditable` mode
|
||||
$('#inputor').caret('position', fixPos);
|
||||
|
||||
// Get offset of the 20th char.
|
||||
// not working in `contentEditable` mode
|
||||
$('#inputor').caret('offset', fixPos);
|
||||
|
||||
// more
|
||||
|
||||
// Get caret position from the first char in the inputor.
|
||||
$('#inputor').caret('pos'); // => 15
|
||||
|
||||
// Set caret position in the inputor
|
||||
// not working in contentEditable mode
|
||||
$('#inputor').caret('pos', 15);
|
||||
|
||||
// set iframe context
|
||||
// NOTE: Related to the iframe's cooridinate.
|
||||
// You might want to get the iframe's offset/position on your own
|
||||
$('#inputor').caret('offset', {iframe: theIframe});
|
||||
$('#inputor').caret('position', {iframe: theIframe});
|
||||
$('#inputor').caret('pos', 15, {iframe: theIframe});
|
||||
|
||||
```
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "Caret.js",
|
||||
"version": "0.2.2",
|
||||
"main": "src/jquery.caret.js",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"spec",
|
||||
"index.html"
|
||||
],
|
||||
"dependencies": {
|
||||
"jquery": ">=1.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jasmine-jquery": "~1.5.8"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "Caret.js",
|
||||
"repo": "ichord/Caret.js",
|
||||
"description": "Add Github like mentions autocomplete to your application.",
|
||||
"version": "0.2.2",
|
||||
"keywords": [
|
||||
"At.js",
|
||||
"caret",
|
||||
"ui"
|
||||
],
|
||||
"dependencies": {
|
||||
"component/jquery": "*"
|
||||
},
|
||||
"demo": "http://ichord.github.com/Caret.js",
|
||||
"main": "src/jquery.caret.js",
|
||||
"scripts": [
|
||||
"src/jquery.caret.js"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
|
@ -0,0 +1,405 @@
|
|||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(["jquery"], function ($) {
|
||||
return (root.returnExportsGlobal = factory($));
|
||||
});
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like enviroments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory(require("jquery"));
|
||||
} else {
|
||||
factory(jQuery);
|
||||
}
|
||||
}(this, function ($) {
|
||||
|
||||
/*
|
||||
Implement Github like autocomplete mentions
|
||||
http://ichord.github.com/At.js
|
||||
|
||||
Copyright (c) 2013 chord.luo@gmail.com
|
||||
Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
/*
|
||||
本插件操作 textarea 或者 input 内的插入符
|
||||
只实现了获得插入符在文本框中的位置,我设置
|
||||
插入符的位置.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy;
|
||||
|
||||
pluginName = 'caret';
|
||||
|
||||
EditableCaret = (function() {
|
||||
function EditableCaret($inputor) {
|
||||
this.$inputor = $inputor;
|
||||
this.domInputor = this.$inputor[0];
|
||||
}
|
||||
|
||||
EditableCaret.prototype.setPos = function(pos) {
|
||||
return this.domInputor;
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getIEPosition = function() {
|
||||
return this.getPosition();
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getPosition = function() {
|
||||
var inputor_offset, offset;
|
||||
offset = this.getOffset();
|
||||
inputor_offset = this.$inputor.offset();
|
||||
offset.left -= inputor_offset.left;
|
||||
offset.top -= inputor_offset.top;
|
||||
return offset;
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getOldIEPos = function() {
|
||||
var preCaretTextRange, textRange;
|
||||
textRange = oDocument.selection.createRange();
|
||||
preCaretTextRange = oDocument.body.createTextRange();
|
||||
preCaretTextRange.moveToElementText(this.domInputor);
|
||||
preCaretTextRange.setEndPoint("EndToEnd", textRange);
|
||||
return preCaretTextRange.text.length;
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getPos = function() {
|
||||
var clonedRange, pos, range;
|
||||
if (range = this.range()) {
|
||||
clonedRange = range.cloneRange();
|
||||
clonedRange.selectNodeContents(this.domInputor);
|
||||
clonedRange.setEnd(range.endContainer, range.endOffset);
|
||||
pos = clonedRange.toString().length;
|
||||
clonedRange.detach();
|
||||
return pos;
|
||||
} else if (oDocument.selection) {
|
||||
return this.getOldIEPos();
|
||||
}
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getOldIEOffset = function() {
|
||||
var range, rect;
|
||||
range = oDocument.selection.createRange().duplicate();
|
||||
range.moveStart("character", -1);
|
||||
rect = range.getBoundingClientRect();
|
||||
return {
|
||||
height: rect.bottom - rect.top,
|
||||
left: rect.left,
|
||||
top: rect.top
|
||||
};
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getOffset = function(pos) {
|
||||
var clonedRange, offset, range, rect, shadowCaret;
|
||||
if (oWindow.getSelection && (range = this.range())) {
|
||||
if (range.endOffset - 1 > 0 && range.endContainer === !this.domInputor) {
|
||||
clonedRange = range.cloneRange();
|
||||
clonedRange.setStart(range.endContainer, range.endOffset - 1);
|
||||
clonedRange.setEnd(range.endContainer, range.endOffset);
|
||||
rect = clonedRange.getBoundingClientRect();
|
||||
offset = {
|
||||
height: rect.height,
|
||||
left: rect.left + rect.width,
|
||||
top: rect.top
|
||||
};
|
||||
clonedRange.detach();
|
||||
}
|
||||
if (!offset || (offset != null ? offset.height : void 0) === 0) {
|
||||
clonedRange = range.cloneRange();
|
||||
shadowCaret = $(oDocument.createTextNode("|"));
|
||||
clonedRange.insertNode(shadowCaret[0]);
|
||||
clonedRange.selectNode(shadowCaret[0]);
|
||||
rect = clonedRange.getBoundingClientRect();
|
||||
offset = {
|
||||
height: rect.height,
|
||||
left: rect.left,
|
||||
top: rect.top
|
||||
};
|
||||
shadowCaret.remove();
|
||||
clonedRange.detach();
|
||||
}
|
||||
} else if (oDocument.selection) {
|
||||
offset = this.getOldIEOffset();
|
||||
}
|
||||
if (offset) {
|
||||
offset.top += $(oWindow).scrollTop();
|
||||
offset.left += $(oWindow).scrollLeft();
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
EditableCaret.prototype.range = function() {
|
||||
var sel;
|
||||
if (!oWindow.getSelection) {
|
||||
return;
|
||||
}
|
||||
sel = oWindow.getSelection();
|
||||
if (sel.rangeCount > 0) {
|
||||
return sel.getRangeAt(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return EditableCaret;
|
||||
|
||||
})();
|
||||
|
||||
InputCaret = (function() {
|
||||
function InputCaret($inputor) {
|
||||
this.$inputor = $inputor;
|
||||
this.domInputor = this.$inputor[0];
|
||||
}
|
||||
|
||||
InputCaret.prototype.getIEPos = function() {
|
||||
var endRange, inputor, len, normalizedValue, pos, range, textInputRange;
|
||||
inputor = this.domInputor;
|
||||
range = oDocument.selection.createRange();
|
||||
pos = 0;
|
||||
if (range && range.parentElement() === inputor) {
|
||||
normalizedValue = inputor.value.replace(/\r\n/g, "\n");
|
||||
len = normalizedValue.length;
|
||||
textInputRange = inputor.createTextRange();
|
||||
textInputRange.moveToBookmark(range.getBookmark());
|
||||
endRange = inputor.createTextRange();
|
||||
endRange.collapse(false);
|
||||
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
||||
pos = len;
|
||||
} else {
|
||||
pos = -textInputRange.moveStart("character", -len);
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
};
|
||||
|
||||
InputCaret.prototype.getPos = function() {
|
||||
if (oDocument.selection) {
|
||||
return this.getIEPos();
|
||||
} else {
|
||||
return this.domInputor.selectionStart;
|
||||
}
|
||||
};
|
||||
|
||||
InputCaret.prototype.setPos = function(pos) {
|
||||
var inputor, range;
|
||||
inputor = this.domInputor;
|
||||
if (oDocument.selection) {
|
||||
range = inputor.createTextRange();
|
||||
range.move("character", pos);
|
||||
range.select();
|
||||
} else if (inputor.setSelectionRange) {
|
||||
inputor.setSelectionRange(pos, pos);
|
||||
}
|
||||
return inputor;
|
||||
};
|
||||
|
||||
InputCaret.prototype.getIEOffset = function(pos) {
|
||||
var h, textRange, x, y;
|
||||
textRange = this.domInputor.createTextRange();
|
||||
pos || (pos = this.getPos());
|
||||
textRange.move('character', pos);
|
||||
x = textRange.boundingLeft;
|
||||
y = textRange.boundingTop;
|
||||
h = textRange.boundingHeight;
|
||||
return {
|
||||
left: x,
|
||||
top: y,
|
||||
height: h
|
||||
};
|
||||
};
|
||||
|
||||
InputCaret.prototype.getOffset = function(pos) {
|
||||
var $inputor, offset, position;
|
||||
$inputor = this.$inputor;
|
||||
if (oDocument.selection) {
|
||||
offset = this.getIEOffset(pos);
|
||||
offset.top += $(oWindow).scrollTop() + $inputor.scrollTop();
|
||||
offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft();
|
||||
return offset;
|
||||
} else {
|
||||
offset = $inputor.offset();
|
||||
position = this.getPosition(pos);
|
||||
return offset = {
|
||||
left: offset.left + position.left - $inputor.scrollLeft(),
|
||||
top: offset.top + position.top - $inputor.scrollTop(),
|
||||
height: position.height
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
InputCaret.prototype.getPosition = function(pos) {
|
||||
var $inputor, at_rect, end_range, format, html, mirror, start_range;
|
||||
$inputor = this.$inputor;
|
||||
format = function(value) {
|
||||
value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, "<br/>");
|
||||
if (/firefox/i.test(navigator.userAgent)) {
|
||||
value = value.replace(/\s/g, ' ');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
if (pos === void 0) {
|
||||
pos = this.getPos();
|
||||
}
|
||||
start_range = $inputor.val().slice(0, pos);
|
||||
end_range = $inputor.val().slice(pos);
|
||||
html = "<span style='position: relative; display: inline;'>" + format(start_range) + "</span>";
|
||||
html += "<span id='caret' style='position: relative; display: inline;'>|</span>";
|
||||
html += "<span style='position: relative; display: inline;'>" + format(end_range) + "</span>";
|
||||
mirror = new Mirror($inputor);
|
||||
return at_rect = mirror.create(html).rect();
|
||||
};
|
||||
|
||||
InputCaret.prototype.getIEPosition = function(pos) {
|
||||
var h, inputorOffset, offset, x, y;
|
||||
offset = this.getIEOffset(pos);
|
||||
inputorOffset = this.$inputor.offset();
|
||||
x = offset.left - inputorOffset.left;
|
||||
y = offset.top - inputorOffset.top;
|
||||
h = offset.height;
|
||||
return {
|
||||
left: x,
|
||||
top: y,
|
||||
height: h
|
||||
};
|
||||
};
|
||||
|
||||
return InputCaret;
|
||||
|
||||
})();
|
||||
|
||||
Mirror = (function() {
|
||||
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"];
|
||||
|
||||
function Mirror($inputor) {
|
||||
this.$inputor = $inputor;
|
||||
}
|
||||
|
||||
Mirror.prototype.mirrorCss = function() {
|
||||
var css,
|
||||
_this = this;
|
||||
css = {
|
||||
position: 'absolute',
|
||||
left: -9999,
|
||||
top: 0,
|
||||
zIndex: -20000
|
||||
};
|
||||
if (this.$inputor.prop('tagName') === 'TEXTAREA') {
|
||||
this.css_attr.push('width');
|
||||
}
|
||||
$.each(this.css_attr, function(i, p) {
|
||||
return css[p] = _this.$inputor.css(p);
|
||||
});
|
||||
return css;
|
||||
};
|
||||
|
||||
Mirror.prototype.create = function(html) {
|
||||
this.$mirror = $('<div></div>');
|
||||
this.$mirror.css(this.mirrorCss());
|
||||
this.$mirror.html(html);
|
||||
this.$inputor.after(this.$mirror);
|
||||
return this;
|
||||
};
|
||||
|
||||
Mirror.prototype.rect = function() {
|
||||
var $flag, pos, rect;
|
||||
$flag = this.$mirror.find("#caret");
|
||||
pos = $flag.position();
|
||||
rect = {
|
||||
left: pos.left,
|
||||
top: pos.top,
|
||||
height: $flag.height()
|
||||
};
|
||||
this.$mirror.remove();
|
||||
return rect;
|
||||
};
|
||||
|
||||
return Mirror;
|
||||
|
||||
})();
|
||||
|
||||
Utils = {
|
||||
contentEditable: function($inputor) {
|
||||
return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true');
|
||||
}
|
||||
};
|
||||
|
||||
methods = {
|
||||
pos: function(pos) {
|
||||
if (pos || pos === 0) {
|
||||
return this.setPos(pos);
|
||||
} else {
|
||||
return this.getPos();
|
||||
}
|
||||
},
|
||||
position: function(pos) {
|
||||
if (oDocument.selection) {
|
||||
return this.getIEPosition(pos);
|
||||
} else {
|
||||
return this.getPosition(pos);
|
||||
}
|
||||
},
|
||||
offset: function(pos) {
|
||||
var offset;
|
||||
offset = this.getOffset(pos);
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
|
||||
oDocument = null;
|
||||
|
||||
oWindow = null;
|
||||
|
||||
oFrame = null;
|
||||
|
||||
setContextBy = function(settings) {
|
||||
var iframe;
|
||||
if (iframe = settings != null ? settings.iframe : void 0) {
|
||||
oFrame = iframe;
|
||||
oWindow = iframe.contentWindow;
|
||||
return oDocument = iframe.contentDocument || oWindow.document;
|
||||
} else {
|
||||
oFrame = void 0;
|
||||
oWindow = window;
|
||||
return oDocument = document;
|
||||
}
|
||||
};
|
||||
|
||||
discoveryIframeOf = function($dom) {
|
||||
var error;
|
||||
oDocument = $dom[0].ownerDocument;
|
||||
oWindow = oDocument.defaultView || oDocument.parentWindow;
|
||||
try {
|
||||
return oFrame = oWindow.frameElement;
|
||||
} catch (_error) {
|
||||
error = _error;
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.caret = function(method, value, settings) {
|
||||
var caret;
|
||||
if (methods[method]) {
|
||||
if ($.isPlainObject(value)) {
|
||||
setContextBy(value);
|
||||
value = void 0;
|
||||
} else {
|
||||
setContextBy(settings);
|
||||
}
|
||||
caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this);
|
||||
return methods[method].apply(caret, [value]);
|
||||
} else {
|
||||
return $.error("Method " + method + " does not exist on jQuery.caret");
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.caret.EditableCaret = EditableCaret;
|
||||
|
||||
$.fn.caret.InputCaret = InputCaret;
|
||||
|
||||
$.fn.caret.Utils = Utils;
|
||||
|
||||
$.fn.caret.apis = methods;
|
||||
|
||||
|
||||
}));
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "jquery.caret",
|
||||
"version": "0.2.2",
|
||||
"description": "Get caret position and offset from inputor",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"grunt": "~0.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt-contrib-coffee": "~0.6.4",
|
||||
"grunt-contrib-jasmine": "~0.5.1",
|
||||
"grunt-contrib-uglify": "~0.2.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-json-replace": "~0.1.2",
|
||||
"grunt-umd": "^2.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test --verbose"
|
||||
},
|
||||
"repository": "",
|
||||
"keywords": [
|
||||
"jquery",
|
||||
"caret",
|
||||
"offset",
|
||||
"position"
|
||||
],
|
||||
"author": "Harold.luo <chord.luo@gmail.com>",
|
||||
"license": "MIT"
|
||||
}
|
|
@ -0,0 +1,311 @@
|
|||
###
|
||||
Implement Github like autocomplete mentions
|
||||
http://ichord.github.com/At.js
|
||||
|
||||
Copyright (c) 2013 chord.luo@gmail.com
|
||||
Licensed under the MIT license.
|
||||
###
|
||||
|
||||
###
|
||||
本插件操作 textarea 或者 input 内的插入符
|
||||
只实现了获得插入符在文本框中的位置,我设置
|
||||
插入符的位置.
|
||||
###
|
||||
"use strict";
|
||||
|
||||
pluginName = 'caret'
|
||||
|
||||
class EditableCaret
|
||||
constructor: (@$inputor) ->
|
||||
@domInputor = @$inputor[0]
|
||||
|
||||
# NOTE: Duck type
|
||||
setPos: (pos) -> @domInputor
|
||||
getIEPosition: -> this.getPosition()
|
||||
getPosition: ->
|
||||
offset = this.getOffset()
|
||||
inputor_offset = @$inputor.offset()
|
||||
offset.left -= inputor_offset.left
|
||||
offset.top -= inputor_offset.top
|
||||
offset
|
||||
|
||||
getOldIEPos: ->
|
||||
textRange = oDocument.selection.createRange()
|
||||
preCaretTextRange = oDocument.body.createTextRange()
|
||||
preCaretTextRange.moveToElementText(@domInputor)
|
||||
preCaretTextRange.setEndPoint("EndToEnd", textRange)
|
||||
preCaretTextRange.text.length
|
||||
|
||||
getPos: ->
|
||||
if range = this.range() # Major Browser and IE > 10
|
||||
clonedRange = range.cloneRange()
|
||||
clonedRange.selectNodeContents(@domInputor)
|
||||
clonedRange.setEnd(range.endContainer, range.endOffset)
|
||||
pos = clonedRange.toString().length
|
||||
clonedRange.detach()
|
||||
pos
|
||||
else if oDocument.selection #IE < 9
|
||||
this.getOldIEPos()
|
||||
|
||||
getOldIEOffset: ->
|
||||
range = oDocument.selection.createRange().duplicate()
|
||||
range.moveStart "character", -1
|
||||
rect = range.getBoundingClientRect()
|
||||
{ height: rect.bottom - rect.top, left: rect.left, top: rect.top }
|
||||
|
||||
getOffset: (pos) ->
|
||||
if oWindow.getSelection and range = this.range()
|
||||
# endContainer would be the inputor in Firefox at the begnning of a line
|
||||
if range.endOffset - 1 > 0 and range.endContainer is not @domInputor
|
||||
clonedRange = range.cloneRange()
|
||||
clonedRange.setStart(range.endContainer, range.endOffset - 1)
|
||||
clonedRange.setEnd(range.endContainer, range.endOffset)
|
||||
rect = clonedRange.getBoundingClientRect()
|
||||
offset = { height: rect.height, left: rect.left + rect.width, top: rect.top }
|
||||
clonedRange.detach()
|
||||
# At the begnning of the inputor, the offset height is 0 in Chrome and Safari
|
||||
# This work fine in all browers but except while the inputor break a line into two (wrapped line).
|
||||
# so we can't use it in all cases.
|
||||
if !offset or offset?.height == 0
|
||||
clonedRange = range.cloneRange()
|
||||
shadowCaret = $ oDocument.createTextNode "|"
|
||||
clonedRange.insertNode shadowCaret[0]
|
||||
clonedRange.selectNode shadowCaret[0]
|
||||
rect = clonedRange.getBoundingClientRect()
|
||||
offset = {height: rect.height, left: rect.left, top: rect.top }
|
||||
shadowCaret.remove()
|
||||
clonedRange.detach()
|
||||
else if oDocument.selection # ie < 9
|
||||
offset = this.getOldIEOffset()
|
||||
|
||||
if offset
|
||||
offset.top += $(oWindow).scrollTop()
|
||||
offset.left += $(oWindow).scrollLeft()
|
||||
|
||||
offset
|
||||
|
||||
range: ->
|
||||
return unless oWindow.getSelection
|
||||
sel = oWindow.getSelection()
|
||||
if sel.rangeCount > 0 then sel.getRangeAt(0) else null
|
||||
|
||||
|
||||
class InputCaret
|
||||
|
||||
constructor: (@$inputor) ->
|
||||
@domInputor = @$inputor[0]
|
||||
|
||||
getIEPos: ->
|
||||
# https://github.com/ichord/Caret.js/wiki/Get-pos-of-caret-in-IE
|
||||
inputor = @domInputor
|
||||
range = oDocument.selection.createRange()
|
||||
pos = 0
|
||||
# selection should in the inputor.
|
||||
if range and range.parentElement() is inputor
|
||||
normalizedValue = inputor.value.replace /\r\n/g, "\n"
|
||||
len = normalizedValue.length
|
||||
textInputRange = inputor.createTextRange()
|
||||
textInputRange.moveToBookmark range.getBookmark()
|
||||
endRange = inputor.createTextRange()
|
||||
endRange.collapse false
|
||||
if textInputRange.compareEndPoints("StartToEnd", endRange) > -1
|
||||
pos = len
|
||||
else
|
||||
pos = -textInputRange.moveStart "character", -len
|
||||
pos
|
||||
|
||||
getPos: ->
|
||||
if oDocument.selection then this.getIEPos() else @domInputor.selectionStart
|
||||
|
||||
setPos: (pos) ->
|
||||
inputor = @domInputor
|
||||
if oDocument.selection #IE
|
||||
range = inputor.createTextRange()
|
||||
range.move "character", pos
|
||||
range.select()
|
||||
else if inputor.setSelectionRange
|
||||
inputor.setSelectionRange pos, pos
|
||||
inputor
|
||||
|
||||
getIEOffset: (pos) ->
|
||||
textRange = @domInputor.createTextRange()
|
||||
pos ||= this.getPos()
|
||||
textRange.move('character', pos)
|
||||
|
||||
x = textRange.boundingLeft
|
||||
y = textRange.boundingTop
|
||||
h = textRange.boundingHeight
|
||||
|
||||
{left: x, top: y, height: h}
|
||||
|
||||
getOffset: (pos) ->
|
||||
$inputor = @$inputor
|
||||
if oDocument.selection
|
||||
offset = this.getIEOffset(pos)
|
||||
offset.top += $(oWindow).scrollTop() + $inputor.scrollTop()
|
||||
offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft()
|
||||
offset
|
||||
else
|
||||
offset = $inputor.offset()
|
||||
position = this.getPosition(pos)
|
||||
offset =
|
||||
left: offset.left + position.left - $inputor.scrollLeft()
|
||||
top: offset.top + position.top - $inputor.scrollTop()
|
||||
height: position.height
|
||||
|
||||
getPosition: (pos)->
|
||||
$inputor = @$inputor
|
||||
format = (value) ->
|
||||
value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g,"<br/>")
|
||||
if /firefox/i.test navigator.userAgent
|
||||
value = value.replace(/\s/g, ' ')
|
||||
value
|
||||
pos = this.getPos() if pos is undefined
|
||||
start_range = $inputor.val().slice(0, pos)
|
||||
end_range = $inputor.val().slice(pos)
|
||||
html = "<span style='position: relative; display: inline;'>"+format(start_range)+"</span>"
|
||||
html += "<span id='caret' style='position: relative; display: inline;'>|</span>"
|
||||
html += "<span style='position: relative; display: inline;'>"+format(end_range)+"</span>"
|
||||
|
||||
mirror = new Mirror($inputor)
|
||||
at_rect = mirror.create(html).rect()
|
||||
|
||||
getIEPosition: (pos) ->
|
||||
offset = this.getIEOffset pos
|
||||
inputorOffset = @$inputor.offset()
|
||||
x = offset.left - inputorOffset.left
|
||||
y = offset.top - inputorOffset.top
|
||||
h = offset.height
|
||||
|
||||
{left: x, top: y, height: h}
|
||||
|
||||
# @example
|
||||
# mirror = new Mirror($("textarea#inputor"))
|
||||
# html = "<p>We will get the rect of <span>@</span>icho</p>"
|
||||
# mirror.create(html).rect()
|
||||
class Mirror
|
||||
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",
|
||||
]
|
||||
|
||||
constructor: (@$inputor) ->
|
||||
|
||||
mirrorCss: ->
|
||||
css =
|
||||
position: 'absolute'
|
||||
left: -9999
|
||||
top: 0
|
||||
zIndex: -20000
|
||||
if @$inputor.prop( 'tagName' ) == 'TEXTAREA'
|
||||
@css_attr.push( 'width' )
|
||||
$.each @css_attr, (i,p) =>
|
||||
css[p] = @$inputor.css p
|
||||
css
|
||||
|
||||
create: (html) ->
|
||||
@$mirror = $('<div></div>')
|
||||
@$mirror.css this.mirrorCss()
|
||||
@$mirror.html(html)
|
||||
@$inputor.after(@$mirror)
|
||||
this
|
||||
|
||||
# 获得标记的位置
|
||||
#
|
||||
# @return [Object] 标记的坐标
|
||||
# {left: 0, top: 0, bottom: 0}
|
||||
rect: ->
|
||||
$flag = @$mirror.find "#caret"
|
||||
pos = $flag.position()
|
||||
rect = {left: pos.left, top: pos.top, height: $flag.height() }
|
||||
@$mirror.remove()
|
||||
rect
|
||||
|
||||
Utils =
|
||||
contentEditable: ($inputor)->
|
||||
!!($inputor[0].contentEditable && $inputor[0].contentEditable == 'true')
|
||||
|
||||
methods =
|
||||
pos: (pos) ->
|
||||
if pos or pos == 0
|
||||
this.setPos pos
|
||||
else
|
||||
this.getPos()
|
||||
|
||||
position: (pos) ->
|
||||
if oDocument.selection then this.getIEPosition pos else this.getPosition pos
|
||||
|
||||
offset: (pos) ->
|
||||
offset = this.getOffset(pos)
|
||||
offset
|
||||
|
||||
oDocument = null
|
||||
oWindow = null
|
||||
oFrame = null
|
||||
setContextBy = (settings) ->
|
||||
if iframe = settings?.iframe
|
||||
oFrame = iframe
|
||||
oWindow = iframe.contentWindow
|
||||
oDocument = iframe.contentDocument || oWindow.document
|
||||
else
|
||||
oFrame = undefined
|
||||
oWindow = window
|
||||
oDocument = document
|
||||
discoveryIframeOf = ($dom) ->
|
||||
oDocument = $dom[0].ownerDocument
|
||||
oWindow = oDocument.defaultView || oDocument.parentWindow
|
||||
try
|
||||
oFrame = oWindow.frameElement
|
||||
catch error
|
||||
# throws error in cross-domain iframes
|
||||
|
||||
$.fn.caret = (method, value, settings) ->
|
||||
# http://stackoverflow.com/questions/16010204/get-reference-of-window-object-from-a-dom-element
|
||||
if methods[method]
|
||||
if $.isPlainObject(value)
|
||||
setContextBy value
|
||||
value = undefined
|
||||
else
|
||||
setContextBy settings
|
||||
caret = if Utils.contentEditable(this) then new EditableCaret(this) else new InputCaret(this)
|
||||
methods[method].apply caret, [value]
|
||||
else
|
||||
$.error "Method #{method} does not exist on jQuery.caret"
|
||||
|
||||
|
||||
|
||||
$.fn.caret.EditableCaret = EditableCaret
|
||||
$.fn.caret.InputCaret = InputCaret
|
||||
$.fn.caret.Utils = Utils
|
||||
$.fn.caret.apis = methods
|
|
@ -0,0 +1,406 @@
|
|||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(["jquery"], function ($) {
|
||||
return (root.returnExportsGlobal = factory($));
|
||||
});
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like enviroments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory(require("jquery"));
|
||||
} else {
|
||||
factory(jQuery);
|
||||
}
|
||||
}(this, function ($) {
|
||||
|
||||
//@ sourceMappingURL=jquery.caret.map
|
||||
/*
|
||||
Implement Github like autocomplete mentions
|
||||
http://ichord.github.com/At.js
|
||||
|
||||
Copyright (c) 2013 chord.luo@gmail.com
|
||||
Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
/*
|
||||
本插件操作 textarea 或者 input 内的插入符
|
||||
只实现了获得插入符在文本框中的位置,我设置
|
||||
插入符的位置.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy;
|
||||
|
||||
pluginName = 'caret';
|
||||
|
||||
EditableCaret = (function() {
|
||||
function EditableCaret($inputor) {
|
||||
this.$inputor = $inputor;
|
||||
this.domInputor = this.$inputor[0];
|
||||
}
|
||||
|
||||
EditableCaret.prototype.setPos = function(pos) {
|
||||
return this.domInputor;
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getIEPosition = function() {
|
||||
return this.getPosition();
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getPosition = function() {
|
||||
var inputor_offset, offset;
|
||||
offset = this.getOffset();
|
||||
inputor_offset = this.$inputor.offset();
|
||||
offset.left -= inputor_offset.left;
|
||||
offset.top -= inputor_offset.top;
|
||||
return offset;
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getOldIEPos = function() {
|
||||
var preCaretTextRange, textRange;
|
||||
textRange = oDocument.selection.createRange();
|
||||
preCaretTextRange = oDocument.body.createTextRange();
|
||||
preCaretTextRange.moveToElementText(this.domInputor);
|
||||
preCaretTextRange.setEndPoint("EndToEnd", textRange);
|
||||
return preCaretTextRange.text.length;
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getPos = function() {
|
||||
var clonedRange, pos, range;
|
||||
if (range = this.range()) {
|
||||
clonedRange = range.cloneRange();
|
||||
clonedRange.selectNodeContents(this.domInputor);
|
||||
clonedRange.setEnd(range.endContainer, range.endOffset);
|
||||
pos = clonedRange.toString().length;
|
||||
clonedRange.detach();
|
||||
return pos;
|
||||
} else if (oDocument.selection) {
|
||||
return this.getOldIEPos();
|
||||
}
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getOldIEOffset = function() {
|
||||
var range, rect;
|
||||
range = oDocument.selection.createRange().duplicate();
|
||||
range.moveStart("character", -1);
|
||||
rect = range.getBoundingClientRect();
|
||||
return {
|
||||
height: rect.bottom - rect.top,
|
||||
left: rect.left,
|
||||
top: rect.top
|
||||
};
|
||||
};
|
||||
|
||||
EditableCaret.prototype.getOffset = function(pos) {
|
||||
var clonedRange, offset, range, rect, shadowCaret;
|
||||
if (oWindow.getSelection && (range = this.range())) {
|
||||
if (range.endOffset - 1 > 0 && range.endContainer === !this.domInputor) {
|
||||
clonedRange = range.cloneRange();
|
||||
clonedRange.setStart(range.endContainer, range.endOffset - 1);
|
||||
clonedRange.setEnd(range.endContainer, range.endOffset);
|
||||
rect = clonedRange.getBoundingClientRect();
|
||||
offset = {
|
||||
height: rect.height,
|
||||
left: rect.left + rect.width,
|
||||
top: rect.top
|
||||
};
|
||||
clonedRange.detach();
|
||||
}
|
||||
if (!offset || (offset != null ? offset.height : void 0) === 0) {
|
||||
clonedRange = range.cloneRange();
|
||||
shadowCaret = $(oDocument.createTextNode("|"));
|
||||
clonedRange.insertNode(shadowCaret[0]);
|
||||
clonedRange.selectNode(shadowCaret[0]);
|
||||
rect = clonedRange.getBoundingClientRect();
|
||||
offset = {
|
||||
height: rect.height,
|
||||
left: rect.left,
|
||||
top: rect.top
|
||||
};
|
||||
shadowCaret.remove();
|
||||
clonedRange.detach();
|
||||
}
|
||||
} else if (oDocument.selection) {
|
||||
offset = this.getOldIEOffset();
|
||||
}
|
||||
if (offset) {
|
||||
offset.top += $(oWindow).scrollTop();
|
||||
offset.left += $(oWindow).scrollLeft();
|
||||
}
|
||||
return offset;
|
||||
};
|
||||
|
||||
EditableCaret.prototype.range = function() {
|
||||
var sel;
|
||||
if (!oWindow.getSelection) {
|
||||
return;
|
||||
}
|
||||
sel = oWindow.getSelection();
|
||||
if (sel.rangeCount > 0) {
|
||||
return sel.getRangeAt(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return EditableCaret;
|
||||
|
||||
})();
|
||||
|
||||
InputCaret = (function() {
|
||||
function InputCaret($inputor) {
|
||||
this.$inputor = $inputor;
|
||||
this.domInputor = this.$inputor[0];
|
||||
}
|
||||
|
||||
InputCaret.prototype.getIEPos = function() {
|
||||
var endRange, inputor, len, normalizedValue, pos, range, textInputRange;
|
||||
inputor = this.domInputor;
|
||||
range = oDocument.selection.createRange();
|
||||
pos = 0;
|
||||
if (range && range.parentElement() === inputor) {
|
||||
normalizedValue = inputor.value.replace(/\r\n/g, "\n");
|
||||
len = normalizedValue.length;
|
||||
textInputRange = inputor.createTextRange();
|
||||
textInputRange.moveToBookmark(range.getBookmark());
|
||||
endRange = inputor.createTextRange();
|
||||
endRange.collapse(false);
|
||||
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
||||
pos = len;
|
||||
} else {
|
||||
pos = -textInputRange.moveStart("character", -len);
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
};
|
||||
|
||||
InputCaret.prototype.getPos = function() {
|
||||
if (oDocument.selection) {
|
||||
return this.getIEPos();
|
||||
} else {
|
||||
return this.domInputor.selectionStart;
|
||||
}
|
||||
};
|
||||
|
||||
InputCaret.prototype.setPos = function(pos) {
|
||||
var inputor, range;
|
||||
inputor = this.domInputor;
|
||||
if (oDocument.selection) {
|
||||
range = inputor.createTextRange();
|
||||
range.move("character", pos);
|
||||
range.select();
|
||||
} else if (inputor.setSelectionRange) {
|
||||
inputor.setSelectionRange(pos, pos);
|
||||
}
|
||||
return inputor;
|
||||
};
|
||||
|
||||
InputCaret.prototype.getIEOffset = function(pos) {
|
||||
var h, textRange, x, y;
|
||||
textRange = this.domInputor.createTextRange();
|
||||
pos || (pos = this.getPos());
|
||||
textRange.move('character', pos);
|
||||
x = textRange.boundingLeft;
|
||||
y = textRange.boundingTop;
|
||||
h = textRange.boundingHeight;
|
||||
return {
|
||||
left: x,
|
||||
top: y,
|
||||
height: h
|
||||
};
|
||||
};
|
||||
|
||||
InputCaret.prototype.getOffset = function(pos) {
|
||||
var $inputor, offset, position;
|
||||
$inputor = this.$inputor;
|
||||
if (oDocument.selection) {
|
||||
offset = this.getIEOffset(pos);
|
||||
offset.top += $(oWindow).scrollTop() + $inputor.scrollTop();
|
||||
offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft();
|
||||
return offset;
|
||||
} else {
|
||||
offset = $inputor.offset();
|
||||
position = this.getPosition(pos);
|
||||
return offset = {
|
||||
left: offset.left + position.left - $inputor.scrollLeft(),
|
||||
top: offset.top + position.top - $inputor.scrollTop(),
|
||||
height: position.height
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
InputCaret.prototype.getPosition = function(pos) {
|
||||
var $inputor, at_rect, end_range, format, html, mirror, start_range;
|
||||
$inputor = this.$inputor;
|
||||
format = function(value) {
|
||||
value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, "<br/>");
|
||||
if (/firefox/i.test(navigator.userAgent)) {
|
||||
value = value.replace(/\s/g, ' ');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
if (pos === void 0) {
|
||||
pos = this.getPos();
|
||||
}
|
||||
start_range = $inputor.val().slice(0, pos);
|
||||
end_range = $inputor.val().slice(pos);
|
||||
html = "<span style='position: relative; display: inline;'>" + format(start_range) + "</span>";
|
||||
html += "<span id='caret' style='position: relative; display: inline;'>|</span>";
|
||||
html += "<span style='position: relative; display: inline;'>" + format(end_range) + "</span>";
|
||||
mirror = new Mirror($inputor);
|
||||
return at_rect = mirror.create(html).rect();
|
||||
};
|
||||
|
||||
InputCaret.prototype.getIEPosition = function(pos) {
|
||||
var h, inputorOffset, offset, x, y;
|
||||
offset = this.getIEOffset(pos);
|
||||
inputorOffset = this.$inputor.offset();
|
||||
x = offset.left - inputorOffset.left;
|
||||
y = offset.top - inputorOffset.top;
|
||||
h = offset.height;
|
||||
return {
|
||||
left: x,
|
||||
top: y,
|
||||
height: h
|
||||
};
|
||||
};
|
||||
|
||||
return InputCaret;
|
||||
|
||||
})();
|
||||
|
||||
Mirror = (function() {
|
||||
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"];
|
||||
|
||||
function Mirror($inputor) {
|
||||
this.$inputor = $inputor;
|
||||
}
|
||||
|
||||
Mirror.prototype.mirrorCss = function() {
|
||||
var css,
|
||||
_this = this;
|
||||
css = {
|
||||
position: 'absolute',
|
||||
left: -9999,
|
||||
top: 0,
|
||||
zIndex: -20000
|
||||
};
|
||||
if (this.$inputor.prop('tagName') === 'TEXTAREA') {
|
||||
this.css_attr.push('width');
|
||||
}
|
||||
$.each(this.css_attr, function(i, p) {
|
||||
return css[p] = _this.$inputor.css(p);
|
||||
});
|
||||
return css;
|
||||
};
|
||||
|
||||
Mirror.prototype.create = function(html) {
|
||||
this.$mirror = $('<div></div>');
|
||||
this.$mirror.css(this.mirrorCss());
|
||||
this.$mirror.html(html);
|
||||
this.$inputor.after(this.$mirror);
|
||||
return this;
|
||||
};
|
||||
|
||||
Mirror.prototype.rect = function() {
|
||||
var $flag, pos, rect;
|
||||
$flag = this.$mirror.find("#caret");
|
||||
pos = $flag.position();
|
||||
rect = {
|
||||
left: pos.left,
|
||||
top: pos.top,
|
||||
height: $flag.height()
|
||||
};
|
||||
this.$mirror.remove();
|
||||
return rect;
|
||||
};
|
||||
|
||||
return Mirror;
|
||||
|
||||
})();
|
||||
|
||||
Utils = {
|
||||
contentEditable: function($inputor) {
|
||||
return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true');
|
||||
}
|
||||
};
|
||||
|
||||
methods = {
|
||||
pos: function(pos) {
|
||||
if (pos || pos === 0) {
|
||||
return this.setPos(pos);
|
||||
} else {
|
||||
return this.getPos();
|
||||
}
|
||||
},
|
||||
position: function(pos) {
|
||||
if (oDocument.selection) {
|
||||
return this.getIEPosition(pos);
|
||||
} else {
|
||||
return this.getPosition(pos);
|
||||
}
|
||||
},
|
||||
offset: function(pos) {
|
||||
var offset;
|
||||
offset = this.getOffset(pos);
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
|
||||
oDocument = null;
|
||||
|
||||
oWindow = null;
|
||||
|
||||
oFrame = null;
|
||||
|
||||
setContextBy = function(settings) {
|
||||
var iframe;
|
||||
if (iframe = settings != null ? settings.iframe : void 0) {
|
||||
oFrame = iframe;
|
||||
oWindow = iframe.contentWindow;
|
||||
return oDocument = iframe.contentDocument || oWindow.document;
|
||||
} else {
|
||||
oFrame = void 0;
|
||||
oWindow = window;
|
||||
return oDocument = document;
|
||||
}
|
||||
};
|
||||
|
||||
discoveryIframeOf = function($dom) {
|
||||
var error;
|
||||
oDocument = $dom[0].ownerDocument;
|
||||
oWindow = oDocument.defaultView || oDocument.parentWindow;
|
||||
try {
|
||||
return oFrame = oWindow.frameElement;
|
||||
} catch (_error) {
|
||||
error = _error;
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.caret = function(method, value, settings) {
|
||||
var caret;
|
||||
if (methods[method]) {
|
||||
if ($.isPlainObject(value)) {
|
||||
setContextBy(value);
|
||||
value = void 0;
|
||||
} else {
|
||||
setContextBy(settings);
|
||||
}
|
||||
caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this);
|
||||
return methods[method].apply(caret, [value]);
|
||||
} else {
|
||||
return $.error("Method " + method + " does not exist on jQuery.caret");
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.caret.EditableCaret = EditableCaret;
|
||||
|
||||
$.fn.caret.InputCaret = InputCaret;
|
||||
|
||||
$.fn.caret.Utils = Utils;
|
||||
|
||||
$.fn.caret.apis = methods;
|
||||
|
||||
|
||||
}));
|
Загрузка…
Ссылка в новой задаче