jquery-atwho-rails 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +1 -1
- data/lib/assets/javascripts/jquery.atwho.js +556 -533
- data/lib/assets/stylesheets/jquery.atwho.css +8 -7
- data/lib/jquery-atwho-rails/version.rb +1 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -24,557 +24,580 @@
|
|
24
24
|
*/
|
25
25
|
|
26
26
|
(function($) {
|
27
|
-
|
28
|
-
* 复制它的大小形状相关的样式. */
|
27
|
+
var At, AtView, Mirror, log, _DEFAULT_TPL, _evalTpl, _highlighter, _isNil, _objectify, _sorter, _unique;
|
29
28
|
Mirror = function($origin) {
|
30
|
-
|
31
|
-
|
29
|
+
this.init($origin);
|
30
|
+
return this;
|
31
|
+
};
|
32
32
|
Mirror.prototype = {
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
33
|
+
$mirror: null,
|
34
|
+
css: ["overflowY", "height", "width", "paddingTop", "paddingLeft", "paddingRight", "paddingBottom", "marginTop", "marginLeft", "marginRight", "marginBottom", 'fontFamily', 'borderStyle', 'borderWidth', 'wordWrap', 'fontSize', 'lineHeight', 'overflowX'],
|
35
|
+
init: function($origin) {
|
36
|
+
var $mirror, css;
|
37
|
+
$mirror = $('<div></div>');
|
38
|
+
css = {
|
39
|
+
opacity: 0,
|
40
|
+
position: 'absolute',
|
41
|
+
left: 0,
|
42
|
+
top: 0,
|
43
|
+
zIndex: -20000,
|
44
|
+
'white-space': 'pre-wrap'
|
45
|
+
};
|
46
|
+
$.each(this.css, function(i, p) {
|
47
|
+
return css[p] = $origin.css(p);
|
48
|
+
});
|
49
|
+
$mirror.css(css);
|
50
|
+
$('body').append($mirror);
|
51
|
+
return this.$mirror = $mirror;
|
52
|
+
},
|
53
|
+
setContent: function(html) {
|
54
|
+
return this.$mirror.html(html);
|
55
|
+
},
|
56
|
+
getFlagRect: function() {
|
57
|
+
var $flag, pos;
|
58
|
+
$flag = this.$mirror.find("span#flag");
|
59
|
+
pos = $flag.position();
|
60
|
+
return {
|
61
|
+
left: pos.left,
|
62
|
+
top: pos.top,
|
63
|
+
bottom: $flag.height() + pos.top
|
64
|
+
};
|
65
|
+
},
|
66
|
+
height: function() {
|
67
|
+
return this.$mirror.height();
|
68
|
+
}
|
69
|
+
};
|
70
|
+
At = function(inputor) {
|
71
|
+
var $inputor,
|
72
|
+
_this = this;
|
73
|
+
$inputor = this.$inputor = $(inputor);
|
74
|
+
this.options = {};
|
75
|
+
this.query = {
|
76
|
+
text: "",
|
77
|
+
start: 0,
|
78
|
+
stop: 0
|
79
|
+
};
|
80
|
+
this._cache = {};
|
81
|
+
this.pos = 0;
|
82
|
+
this.flags = {};
|
83
|
+
this.theflag = null;
|
84
|
+
this.search_word = {};
|
85
|
+
this.view = AtView;
|
86
|
+
this.mirror = new Mirror($inputor);
|
87
|
+
$inputor.on("keyup.inputor", function(e) {
|
88
|
+
var lookup, stop;
|
89
|
+
stop = e.keyCode === 40 || e.keyCode === 38;
|
90
|
+
lookup = !(stop && _this.view.isShowing());
|
91
|
+
if (lookup) return _this.lookup();
|
92
|
+
}).on("mouseup.inputor", function(e) {
|
93
|
+
return _this.lookup();
|
94
|
+
});
|
95
|
+
this.init();
|
96
|
+
log("At.new", $inputor[0]);
|
97
|
+
return this;
|
61
98
|
};
|
62
|
-
At = {
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
},
|
84
|
-
|
85
|
-
|
99
|
+
At.prototype = {
|
100
|
+
constructor: At,
|
101
|
+
init: function() {
|
102
|
+
var _this = this;
|
103
|
+
this.$inputor.on('keydown.inputor', function(e) {
|
104
|
+
return _this.onkeydown(e);
|
105
|
+
}).on('scroll.inputor', function(e) {
|
106
|
+
return _this.view.hide();
|
107
|
+
}).on('blur.inputor', function(e) {
|
108
|
+
return _this.view.hide(1000);
|
109
|
+
});
|
110
|
+
return log("At.init", this.$inputor[0]);
|
111
|
+
},
|
112
|
+
reg: function(flag, options) {
|
113
|
+
var opt;
|
114
|
+
opt = {};
|
115
|
+
if ($.isFunction(options)) {
|
116
|
+
opt['callback'] = options;
|
117
|
+
} else {
|
118
|
+
opt = options;
|
119
|
+
}
|
120
|
+
this.options[flag] = $.extend({}, $.fn.atWho["default"], opt);
|
121
|
+
return log("At.reg", this.$inputor[0], flag, options);
|
122
|
+
},
|
123
|
+
dataValue: function() {
|
124
|
+
var match, search_word;
|
125
|
+
search_word = this.search_word[this.theflag];
|
126
|
+
if (search_word) return search_word;
|
127
|
+
match = /data-value=['?]\$\{(\w+)\}/g.exec(this.getOpt('tpl'));
|
128
|
+
return this.search_word[this.theflag] = !_isNil(match) ? match[1] : null;
|
129
|
+
},
|
130
|
+
getOpt: function(key) {
|
131
|
+
try {
|
132
|
+
return this.options[this.theflag][key];
|
133
|
+
} catch (error) {
|
134
|
+
return null;
|
135
|
+
}
|
136
|
+
},
|
137
|
+
rect: function() {
|
138
|
+
var $inputor, Sel, at_rect, bottom, end_range, format, html, mirror, offset, start_range, text, x, y;
|
139
|
+
$inputor = this.$inputor;
|
140
|
+
if (document.selection) {
|
141
|
+
Sel = document.selection.createRange();
|
142
|
+
x = Sel.boundingLeft + $inputor.scrollLeft();
|
143
|
+
y = Sel.boundingTop + $(window).scrollTop() + $inputor.scrollTop();
|
144
|
+
bottom = y + Sel.boundingHeight;
|
145
|
+
return {
|
146
|
+
top: y,
|
147
|
+
left: x,
|
148
|
+
bottom: bottom
|
149
|
+
};
|
150
|
+
}
|
151
|
+
mirror = this.mirror;
|
152
|
+
format = function(value) {
|
153
|
+
return value.replace(/</g, '<').replace(/>/g, '>').replace(/`/g, '`').replace(/"/g, '"').replace(/\r\n|\r|\n/g, "<br />");
|
154
|
+
};
|
155
|
+
/* 克隆完inputor后将原来的文本内容根据
|
156
|
+
@的位置进行分块,以获取@块在inputor(输入框)里的position
|
157
|
+
*/
|
158
|
+
text = $inputor.val();
|
159
|
+
start_range = text.slice(0, this.pos - 1);
|
160
|
+
end_range = text.slice(this.pos + 1);
|
161
|
+
html = "<span>" + format(start_range) + "</span>";
|
162
|
+
html += "<span id='flag'>@</span>";
|
163
|
+
html += "<span>" + format(end_range) + "</span>";
|
164
|
+
mirror.setContent(html);
|
165
|
+
/*
|
166
|
+
将inputor的 offset(相对于document)
|
167
|
+
和@在inputor里的position相加
|
168
|
+
就得到了@相对于document的offset.
|
169
|
+
当然,还要加上行高和滚动条的偏移量.
|
170
|
+
*/
|
171
|
+
offset = $inputor.offset();
|
172
|
+
at_rect = mirror.getFlagRect();
|
173
|
+
/*
|
174
|
+
FIXME: -$(window).scrollTop() get "wrong" offset.
|
175
|
+
but is good for $inputor.scrollTop()
|
176
|
+
jquey 1. + 07.1 fixed the scrollTop problem!?
|
177
|
+
*/
|
178
|
+
x = offset.left + at_rect.left - $inputor.scrollLeft();
|
179
|
+
y = offset.top - $inputor.scrollTop();
|
180
|
+
bottom = y + at_rect.bottom;
|
181
|
+
y += at_rect.top;
|
182
|
+
return {
|
183
|
+
top: y,
|
184
|
+
left: x,
|
185
|
+
bottom: bottom
|
186
|
+
};
|
187
|
+
},
|
188
|
+
cache: function(value) {
|
189
|
+
var key, _base;
|
190
|
+
key = this.query.text;
|
191
|
+
if (!this.getOpt("cache") || !key) return null;
|
192
|
+
return (_base = this._cache)[key] || (_base[key] = value);
|
193
|
+
},
|
194
|
+
getKeyname: function() {
|
195
|
+
var $inputor, caret_pos, end, key, matched, start, subtext, text,
|
196
|
+
_this = this;
|
197
|
+
$inputor = this.$inputor;
|
198
|
+
text = $inputor.val();
|
199
|
+
caret_pos = $inputor.caretPos();
|
200
|
+
/* 向在插入符前的的文本进行正则匹配
|
201
|
+
* 考虑会有多个 @ 的存在, 匹配离插入符最近的一个
|
202
|
+
*/
|
203
|
+
subtext = text.slice(0, caret_pos);
|
204
|
+
matched = null;
|
205
|
+
$.each(this.options, function(flag) {
|
206
|
+
var match, regexp;
|
207
|
+
regexp = new RegExp(flag + '([A-Za-z0-9_\+\-]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi');
|
208
|
+
match = regexp.exec(subtext);
|
209
|
+
if (!_isNil(match)) {
|
210
|
+
matched = match[1] === 'undefined' ? match[2] : match[1];
|
211
|
+
_this.theflag = flag;
|
212
|
+
return false;
|
213
|
+
}
|
214
|
+
});
|
215
|
+
if (typeof matched === 'string' && matched.length <= 20) {
|
216
|
+
start = caret_pos - matched.length;
|
217
|
+
end = start + matched.length;
|
218
|
+
this.pos = start;
|
219
|
+
key = {
|
220
|
+
'text': matched.toLowerCase(),
|
221
|
+
'start': start,
|
222
|
+
'end': end
|
223
|
+
};
|
224
|
+
} else {
|
225
|
+
this.view.hide();
|
226
|
+
}
|
227
|
+
log("At.getKeyname", key);
|
228
|
+
return this.query = key;
|
229
|
+
},
|
230
|
+
replaceStr: function(str) {
|
231
|
+
var $inputor, key, source, start_str, text;
|
232
|
+
$inputor = this.$inputor;
|
233
|
+
key = this.query;
|
234
|
+
source = $inputor.val();
|
235
|
+
start_str = source.slice(0, key.start);
|
236
|
+
text = start_str + str + source.slice(key.end);
|
237
|
+
$inputor.val(text);
|
238
|
+
$inputor.caretPos(start_str.length + str.length);
|
239
|
+
$inputor.change();
|
240
|
+
return log("At.replaceStr", text);
|
241
|
+
},
|
242
|
+
onkeydown: function(e) {
|
243
|
+
var view;
|
244
|
+
view = this.view;
|
245
|
+
if (!view.isShowing()) return;
|
246
|
+
switch (e.keyCode) {
|
247
|
+
case 38:
|
248
|
+
e.preventDefault();
|
249
|
+
view.prev();
|
250
|
+
break;
|
251
|
+
case 40:
|
252
|
+
e.preventDefault();
|
253
|
+
view.next();
|
254
|
+
break;
|
255
|
+
case 9:
|
256
|
+
case 13:
|
257
|
+
if (!view.isShowing()) return;
|
258
|
+
e.preventDefault();
|
259
|
+
view.choose();
|
260
|
+
break;
|
261
|
+
default:
|
262
|
+
$.noop();
|
263
|
+
}
|
264
|
+
return e.stopPropagation();
|
265
|
+
},
|
266
|
+
renderView: function(datas) {
|
267
|
+
log("At.renderView", this, datas);
|
268
|
+
datas = datas.splice(0, this.getOpt('limit'));
|
269
|
+
datas = _unique(datas, this.dataValue());
|
270
|
+
datas = _objectify(datas);
|
271
|
+
datas = _sorter.call(this, datas);
|
272
|
+
return this.view.render(this, datas);
|
273
|
+
},
|
274
|
+
lookup: function() {
|
275
|
+
var callback, datas, key;
|
276
|
+
key = this.getKeyname();
|
277
|
+
if (!key) return false;
|
278
|
+
log("At.lookup.key", key);
|
279
|
+
if (!_isNil(datas = this.cache())) {
|
280
|
+
this.renderView(datas);
|
281
|
+
} else if (!_isNil(datas = this.lookupWithData(key))) {
|
282
|
+
this.renderView(datas);
|
283
|
+
} else if ($.isFunction(callback = this.getOpt('callback'))) {
|
284
|
+
callback(key.text, $.proxy(this.renderView, this));
|
285
|
+
} else {
|
286
|
+
this.view.hide();
|
287
|
+
}
|
288
|
+
return $.noop();
|
289
|
+
},
|
290
|
+
lookupWithData: function(key) {
|
291
|
+
var data, items,
|
292
|
+
_this = this;
|
293
|
+
data = this.getOpt("data");
|
294
|
+
if ($.isArray(data) && data.length !== 0) {
|
295
|
+
items = $.map(data, function(item, i) {
|
296
|
+
var match, name, regexp;
|
86
297
|
try {
|
87
|
-
|
298
|
+
name = $.isPlainObject(item) ? item[_this.dataValue()] : item;
|
299
|
+
regexp = new RegExp(key.text.replace("+", "\\+"), 'i');
|
300
|
+
match = name.match(regexp);
|
88
301
|
} catch (e) {
|
89
|
-
|
302
|
+
return null;
|
90
303
|
}
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
if (document.selection) {// IE!!!
|
97
|
-
var Sel = document.selection.createRange();
|
98
|
-
x = Sel.boundingLeft + $inputor.scrollLeft();
|
99
|
-
y = Sel.boundingTop + Sel.boundingHeight
|
100
|
-
+ $(window).scrollTop() + $inputor.scrollTop();
|
101
|
-
return {'top':y,'left':x};
|
102
|
-
}
|
103
|
-
|
104
|
-
mirror = $inputor.data("mirror");
|
105
|
-
if (_isNil(mirror)) {
|
106
|
-
mirror = new Mirror($inputor);
|
107
|
-
$inputor.data("mirror",mirror);
|
108
|
-
}
|
109
|
-
|
110
|
-
/* 为了将textarea中文字的排版模拟到镜像div中
|
111
|
-
* 我们需要做一些字符处理.由于div元素中不认多余的空格.
|
112
|
-
* 我们需要将多余的空格用元素块包裹.
|
113
|
-
× 换行符直接替换成<br/>就可以了.
|
114
|
-
* NOTE: “\r\n” 用于ie的textarea.
|
115
|
-
*/
|
116
|
-
function format(value) {
|
117
|
-
value = value.replace(/</g, '<')
|
118
|
-
.replace(/>/g, '>')
|
119
|
-
.replace(/`/g,'`')
|
120
|
-
.replace(/"/g,'"');
|
121
|
-
return value.replace(/\r\n|\r|\n/g,"<br />");
|
122
|
-
}
|
123
|
-
/* 克隆完inputor后将原来的文本内容根据
|
124
|
-
* @的位置进行分块,以获取@块在inputor(输入框)里的position
|
125
|
-
* */
|
126
|
-
text = $inputor.val();
|
127
|
-
start_range = text.slice(0,this.pos - 1);
|
128
|
-
end_range = text.slice(this.pos + 1);
|
129
|
-
html = "<span>"+format(start_range)+"</span>";
|
130
|
-
html += "<span id='flag'>@</span>";
|
131
|
-
html += "<span>"+format(end_range)+"</span>";
|
132
|
-
mirror.setContent(html);
|
133
|
-
|
134
|
-
/* 将inputor的 offset(相对于document)
|
135
|
-
* 和@在inputor里的position相加
|
136
|
-
* 就得到了@相对于document的offset.
|
137
|
-
* 当然,还要加上行高和滚动条的偏移量.
|
138
|
-
* */
|
139
|
-
offset = $inputor.offset();
|
140
|
-
at_pos = mirror.getFlagPos();
|
141
|
-
line_height = $inputor.css("line-height");
|
142
|
-
line_height = isNaN(line_height) ? 20 : line_height;
|
143
|
-
//FIXME: -$(window).scrollTop() get "wrong" offset.
|
144
|
-
// but is good for $inputor.scrollTop();
|
145
|
-
// jquey 1. + 07.1 fixed the scrollTop problem!?
|
146
|
-
y = offset.top + at_pos.top + line_height - $inputor.scrollTop();
|
147
|
-
x = offset.left + at_pos.left - $inputor.scrollLeft();
|
148
|
-
|
149
|
-
return {'top':y,'left':x};
|
150
|
-
},
|
151
|
-
cache: function(key,value) {
|
152
|
-
if (!this.getOpt('cache')) return null;
|
153
|
-
_log("cacheing",key,value);
|
154
|
-
if (value)
|
155
|
-
this._cache[key] = value;
|
156
|
-
return this._cache[key];
|
157
|
-
},
|
158
|
-
getKeyname: function() {
|
159
|
-
$inputor = this.$inputor;
|
160
|
-
text = $inputor.val();
|
161
|
-
//获得inputor中插入符的position.
|
162
|
-
caret_pos = $inputor.caretPos();
|
163
|
-
/* 向在插入符前的的文本进行正则匹配
|
164
|
-
* 考虑会有多个 @ 的存在, 匹配离插入符最近的一个*/
|
165
|
-
subtext = text.slice(0,caret_pos);
|
166
|
-
// word = subtext.exec(/@(\w+)$|@[^\x00-\xff]+$/g);
|
167
|
-
var self = this;
|
168
|
-
var matched = null;
|
169
|
-
$.each(this.options,function(flag) {
|
170
|
-
regexp = new RegExp(flag+'([A-Za-z0-9_\+\-]*)$|'+flag+'([^\\x00-\\xff]*)$','gi');
|
171
|
-
match = regexp.exec(subtext);
|
172
|
-
if (!_isNil(match)) {
|
173
|
-
matched = match[1] == undefined ? match[2] : match[1];
|
174
|
-
self.theflag = flag;
|
175
|
-
return false;
|
176
|
-
}
|
177
|
-
});
|
178
|
-
var key = null;
|
179
|
-
_log("matched",matched);
|
180
|
-
if (typeof matched == "string" && matched.length <= 20) {
|
181
|
-
start = caret_pos - matched.length;
|
182
|
-
end = start + matched.length;
|
183
|
-
this.pos = start;
|
184
|
-
key = {'text':matched, 'start':start, 'end':end};
|
185
|
-
} else
|
186
|
-
this.view.hide();
|
187
|
-
|
188
|
-
this.keyword = key;
|
189
|
-
_log("getKeyname",key);
|
190
|
-
return key;
|
191
|
-
},
|
192
|
-
/* 捕捉inputor的上下回车键.
|
193
|
-
* 在列表框做相应的操作,上下滚动,回车选择
|
194
|
-
* 返回 false 阻止冒泡事件以捕捉inputor对应的事件
|
195
|
-
* */
|
196
|
-
onkeydown:function(e) {
|
197
|
-
view = this.view;
|
198
|
-
// 当列表没显示时不捕捉inputor相关事件.
|
199
|
-
if (!view.watching()) return true;
|
200
|
-
last_idx = view.items.length - 1;
|
201
|
-
var return_val = false;
|
202
|
-
switch (e.keyCode) {
|
203
|
-
case 27:
|
204
|
-
this.choose();
|
205
|
-
break;
|
206
|
-
// UP
|
207
|
-
case 38:
|
208
|
-
// if put this line outside the switch
|
209
|
-
// the view will flash when key down.
|
210
|
-
$(view.id + " ul li.cur").removeClass("cur");
|
211
|
-
view.cur_li_idx--;
|
212
|
-
// 到达顶端时高亮效果跳到最后
|
213
|
-
if (view.cur_li_idx < 0)
|
214
|
-
view.cur_li_idx = last_idx;
|
215
|
-
$(view.id + " li:eq(" + view.cur_li_idx + ")")
|
216
|
-
.addClass('cur');
|
217
|
-
break;
|
218
|
-
// DOWN
|
219
|
-
case 40:
|
220
|
-
$(view.id + " ul li.cur").removeClass("cur");
|
221
|
-
view.cur_li_idx++;
|
222
|
-
if (view.cur_li_idx > last_idx)
|
223
|
-
view.cur_li_idx = 0;
|
224
|
-
$(view.id + " li:eq(" + view.cur_li_idx + ")")
|
225
|
-
.addClass('cur');
|
226
|
-
break;
|
227
|
-
//TAB or ENTER
|
228
|
-
case 9:
|
229
|
-
case 13:
|
230
|
-
$(view.id + " ul li.cur").removeClass("cur");
|
231
|
-
// 如果列表为空,则不捕捉回车事件
|
232
|
-
$cur_li = $(view.id + " li:eq("+view.cur_li_idx+")");
|
233
|
-
this.choose($cur_li);
|
234
|
-
break;
|
235
|
-
default:
|
236
|
-
return_val = true;
|
237
|
-
}
|
238
|
-
return return_val;
|
239
|
-
},
|
240
|
-
replaceStr: function(str) {
|
241
|
-
/* $inputor.replaceStr(str,start,end)*/
|
242
|
-
key = this.keyword;
|
243
|
-
source = this.$inputor.val();
|
244
|
-
start_str = source.slice(0, key.start);
|
245
|
-
text = start_str + str + source.slice(key.end);
|
246
|
-
this.$inputor.val(text);
|
247
|
-
this.$inputor.caretPos(start_str.length + str.length);
|
248
|
-
},
|
249
|
-
choose: function($li) {
|
250
|
-
str = _isNil($li) ? this.keyword.text+" " : $li.attr("data-keyname")+" ";
|
251
|
-
this.replaceStr(str);
|
252
|
-
this.view.hide();
|
253
|
-
},
|
254
|
-
reg: function(inputor) {
|
255
|
-
$inputor = $(inputor);
|
256
|
-
|
257
|
-
/* 防止对同一个inputor进行多次绑定
|
258
|
-
* 在每个已经绑定过的inputor设置一个key.
|
259
|
-
* 注册过的key将不再进行绑定
|
260
|
-
* */
|
261
|
-
key = $inputor.data("@reg-key");
|
262
|
-
if ($.inArray(key,this.inputor_keys) >= 0)
|
263
|
-
return null;
|
264
|
-
_log("reg",inputor,key);
|
265
|
-
|
266
|
-
key = "@-"+$.now();
|
267
|
-
$inputor.data("@reg-key",key);
|
268
|
-
this.inputor_keys.push(key);
|
269
|
-
// 捕捉inputor事件
|
270
|
-
var self = this;
|
271
|
-
$inputor.bind("keydown",function(e) {
|
272
|
-
return self.onkeydown(e);
|
273
|
-
})
|
274
|
-
.scroll(function(e){
|
275
|
-
self.view.hide();
|
276
|
-
})
|
277
|
-
.blur(function(e){
|
278
|
-
self.view.timeout_id = setTimeout("At.view.hide()",100);
|
279
|
-
});
|
280
|
-
return key;
|
281
|
-
},
|
282
|
-
watch: function(inputor) {
|
283
|
-
this.$inputor = $(inputor);
|
284
|
-
key = this.getKeyname();
|
285
|
-
if (!key) return false;
|
286
|
-
/*
|
287
|
-
* 支持多渠道获得用户数据.
|
288
|
-
* 可以设置静态数据的同时从服务器动态获取.
|
289
|
-
* 获取级别从先到后: cache -> statis data -> ajax.
|
290
|
-
*/
|
291
|
-
if (!_isNil(names = this.cache(this.keyword.text))) {
|
292
|
-
_log("cache data",names);
|
293
|
-
this.view.load(names,false);
|
294
|
-
} else if (!_isNil(names = this.watchWithData(key))) {
|
295
|
-
_log("statis data",names);
|
296
|
-
this.view.load(names,false);
|
297
|
-
} else if ($.isFunction(callback = this.getOpt('callback'))){
|
298
|
-
_log("callbacking",callback);
|
299
|
-
callback(At);
|
300
|
-
} else
|
301
|
-
this.view.hide();
|
302
|
-
},
|
303
|
-
watchWithData:function(key) {
|
304
|
-
data = this.getOpt("data");
|
305
|
-
_log("watchWithData",data);
|
306
|
-
var items = null;
|
307
|
-
var self = this;
|
308
|
-
if($.isArray(data) && data.length != 0) {
|
309
|
-
items = $.map(data,function(item,i) {
|
310
|
-
//support plain object also
|
311
|
-
var name = $.isPlainObject(item) ? item[self.searchWord()] : item;
|
312
|
-
try {
|
313
|
-
match = name.match((new RegExp(key.text.replace("+","\\+"),"i")));
|
314
|
-
} catch(e) {
|
315
|
-
_log("watchWithData.error",e);
|
316
|
-
return null;
|
317
|
-
}
|
318
|
-
return match ? item : null;
|
319
|
-
});
|
304
|
+
if (match) {
|
305
|
+
return item;
|
306
|
+
} else {
|
307
|
+
return null;
|
320
308
|
}
|
321
|
-
|
322
|
-
return items;
|
309
|
+
});
|
323
310
|
}
|
311
|
+
return items;
|
312
|
+
}
|
324
313
|
};
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
}
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
});
|
406
|
-
this.show();
|
407
|
-
$ul.find("li:eq(0)").addClass("cur");
|
314
|
+
AtView = {
|
315
|
+
timeout_id: null,
|
316
|
+
id: '#at-view',
|
317
|
+
holder: null,
|
318
|
+
_jqo: null,
|
319
|
+
jqo: function() {
|
320
|
+
var jqo;
|
321
|
+
jqo = this._jqo;
|
322
|
+
return jqo = _isNil(jqo) ? (this._jqo = $(this.id)) : jqo;
|
323
|
+
},
|
324
|
+
init: function() {
|
325
|
+
var $menu, tpl,
|
326
|
+
_this = this;
|
327
|
+
if (!_isNil(this.jqo())) return;
|
328
|
+
tpl = "<div id='" + this.id.slice(1) + "' class='at-view'><ul id='" + this.id.slice(1) + "-ul'></ul></div>";
|
329
|
+
$("body").append(tpl);
|
330
|
+
$menu = this.jqo().find('ul');
|
331
|
+
return $menu.on('mouseenter.view', 'li', function(e) {
|
332
|
+
$menu.find('.cur').removeClass('cur');
|
333
|
+
return $(e.currentTarget).addClass('cur');
|
334
|
+
}).on('click', function(e) {
|
335
|
+
e.stopPropagation();
|
336
|
+
e.preventDefault();
|
337
|
+
return _this.choose();
|
338
|
+
});
|
339
|
+
},
|
340
|
+
isShowing: function() {
|
341
|
+
return this.jqo().is(":visible");
|
342
|
+
},
|
343
|
+
choose: function() {
|
344
|
+
var $li, str;
|
345
|
+
$li = this.jqo().find(".cur");
|
346
|
+
str = _isNil($li) ? this.holder.query.text + " " : $li.attr(this.holder.getOpt("choose")) + " ";
|
347
|
+
this.holder.replaceStr(str);
|
348
|
+
return this.hide();
|
349
|
+
},
|
350
|
+
rePosition: function() {
|
351
|
+
var rect;
|
352
|
+
rect = this.holder.rect();
|
353
|
+
if (rect.bottom + this.jqo().height() - $(window).scrollTop() > $(window).height()) {
|
354
|
+
rect.bottom = rect.top - this.jqo().height();
|
355
|
+
}
|
356
|
+
log("AtView.rePosition", {
|
357
|
+
left: rect.left,
|
358
|
+
top: rect.bottom
|
359
|
+
});
|
360
|
+
return this.jqo().offset({
|
361
|
+
left: rect.left,
|
362
|
+
top: rect.bottom
|
363
|
+
});
|
364
|
+
},
|
365
|
+
next: function() {
|
366
|
+
var cur, next;
|
367
|
+
cur = this.jqo().find('.cur').removeClass('cur');
|
368
|
+
next = cur.next();
|
369
|
+
if (!cur.length) next = $(this.jqo().find('li')[0]);
|
370
|
+
return next.addClass('cur');
|
371
|
+
},
|
372
|
+
prev: function() {
|
373
|
+
var cur, prev;
|
374
|
+
cur = this.jqo().find('.cur').removeClass('cur');
|
375
|
+
prev = cur.prev();
|
376
|
+
if (!prev.length) prev = this.jqo().find('li').last();
|
377
|
+
return prev.addClass('cur');
|
378
|
+
},
|
379
|
+
show: function() {
|
380
|
+
if (!this.isShowing()) this.jqo().show();
|
381
|
+
return this.rePosition();
|
382
|
+
},
|
383
|
+
hide: function(time) {
|
384
|
+
var callback,
|
385
|
+
_this = this;
|
386
|
+
if (isNaN(time)) {
|
387
|
+
if (this.isShowing()) return this.jqo().hide();
|
388
|
+
} else {
|
389
|
+
callback = function() {
|
390
|
+
return _this.hide();
|
391
|
+
};
|
392
|
+
clearTimeout(this.timeout_id);
|
393
|
+
return this.timeout_id = setTimeout(callback, 300);
|
408
394
|
}
|
395
|
+
},
|
396
|
+
clear: function(clear_all) {
|
397
|
+
if (clear_all === true) this._cache = {};
|
398
|
+
return this.jqo().find('ul').empty();
|
399
|
+
},
|
400
|
+
render: function(holder, list) {
|
401
|
+
var $ul, tpl;
|
402
|
+
if (!$.isArray(list)) return false;
|
403
|
+
this.holder = holder;
|
404
|
+
holder.cache(list);
|
405
|
+
this.clear();
|
406
|
+
$ul = this.jqo().find('ul');
|
407
|
+
tpl = holder.getOpt('tpl');
|
408
|
+
$.each(list, function(i, item) {
|
409
|
+
var li;
|
410
|
+
tpl || (tpl = _DEFAULT_TPL);
|
411
|
+
li = _evalTpl(tpl, item);
|
412
|
+
log("AtView.render", li);
|
413
|
+
return $ul.append(_highlighter(li, holder.query.text));
|
414
|
+
});
|
415
|
+
this.show();
|
416
|
+
return $ul.find("li:eq(0)").addClass("cur");
|
417
|
+
}
|
409
418
|
};
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
419
|
+
_objectify = function(list) {
|
420
|
+
return $.map(list, function(item, k) {
|
421
|
+
if (!$.isPlainObject(item)) {
|
422
|
+
item = {
|
423
|
+
id: k,
|
424
|
+
name: item
|
425
|
+
};
|
426
|
+
}
|
427
|
+
return item;
|
428
|
+
});
|
429
|
+
};
|
430
|
+
_evalTpl = function(tpl, map) {
|
431
|
+
var el;
|
432
|
+
try {
|
433
|
+
return el = tpl.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
|
434
|
+
return map[key];
|
425
435
|
});
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
436
|
+
} catch (error) {
|
437
|
+
return "";
|
438
|
+
}
|
439
|
+
};
|
440
|
+
_highlighter = function(li, query) {
|
441
|
+
if (_isNil(query)) return li;
|
442
|
+
return li.replace(new RegExp(">\\s*(\\w*)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig'), function(str, $1, $2, $3) {
|
443
|
+
return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
|
444
|
+
});
|
445
|
+
};
|
446
|
+
_sorter = function(items) {
|
447
|
+
var data_value, item, query, results, text, _i, _len;
|
448
|
+
data_value = this.dataValue();
|
449
|
+
query = this.query.text;
|
450
|
+
results = [];
|
451
|
+
for (_i = 0, _len = items.length; _i < _len; _i++) {
|
452
|
+
item = items[_i];
|
453
|
+
text = item[data_value];
|
454
|
+
if (text.toLowerCase().indexOf(query) === -1) continue;
|
455
|
+
item.order = text.toLowerCase().indexOf(query);
|
456
|
+
results.push(item);
|
457
|
+
}
|
458
|
+
results.sort(function(a, b) {
|
459
|
+
return a.order - b.order;
|
460
|
+
});
|
461
|
+
return results;
|
462
|
+
};
|
463
|
+
/*
|
464
|
+
maybe we can use $._unique.
|
465
|
+
But i don't know it will delete li element frequently or not.
|
466
|
+
I think we should not change DOM element frequently.
|
467
|
+
more, It seems batter not to call evalTpl function too much times.
|
468
|
+
*/
|
469
|
+
_unique = function(list, query) {
|
470
|
+
var record;
|
471
|
+
record = [];
|
472
|
+
return $.map(list, function(v, id) {
|
473
|
+
var value;
|
474
|
+
value = $.isPlainObject(v) ? v[query] : v;
|
475
|
+
if ($.inArray(value, record) < 0) {
|
476
|
+
record.push(value);
|
477
|
+
return v;
|
478
|
+
}
|
479
|
+
});
|
480
|
+
};
|
481
|
+
_isNil = function(target) {
|
482
|
+
return !target || ($.isPlainObject(target) && $.isEmptyObject(target)) || ($.isArray(target) && target.length === 0) || (target instanceof $ && target.length === 0) || target === void 0;
|
483
|
+
};
|
484
|
+
_DEFAULT_TPL = "<li id='${id}' data-value='${name}'>${name}</li>";
|
485
|
+
log = function() {};
|
486
|
+
$.fn.atWho = function(flag, options) {
|
487
|
+
AtView.init();
|
488
|
+
return this.filter('textarea, input').each(function() {
|
489
|
+
var $this, data;
|
490
|
+
$this = $(this);
|
491
|
+
data = $this.data("AtWho");
|
492
|
+
if (!data) $this.data('AtWho', (data = new At(this)));
|
493
|
+
return data.reg(flag, options);
|
494
|
+
});
|
495
|
+
};
|
496
|
+
return $.fn.atWho["default"] = {
|
497
|
+
data: [],
|
498
|
+
choose: "data-value",
|
499
|
+
callback: null,
|
500
|
+
cache: true,
|
501
|
+
limit: 5,
|
502
|
+
tpl: _DEFAULT_TPL
|
503
|
+
};
|
504
|
+
})(window.jQuery);
|
460
505
|
|
461
|
-
$.fn.atWho = function (flag,options) {
|
462
|
-
At.options[flag] = _setSettings(options);
|
463
|
-
_log("options",At.options);
|
464
|
-
return this.filter('textarea, input').each(function() {
|
465
|
-
if (!At.reg(this)) return;
|
466
|
-
$(this).bind("keyup",function(e) {
|
467
|
-
/* 当用户列表框显示时, 上下键不触发查询 */
|
468
|
-
var stop_key = e.keyCode == 40 || e.keyCode == 38;
|
469
|
-
watch = !(At.view.watching() && stop_key);
|
470
|
-
if (watch) At.watch(this);
|
471
|
-
})
|
472
|
-
.mouseup(function(e) {
|
473
|
-
At.watch(this);
|
474
|
-
});
|
475
|
-
});
|
476
|
-
return At;
|
477
|
-
}
|
478
|
-
})(window.jQuery);
|
479
506
|
|
480
507
|
/* 本插件操作 textarea 或者 input 内的插入符
|
481
508
|
* 只实现了获得插入符在文本框中的位置,我设置
|
482
509
|
* 插入符的位置.
|
483
510
|
* */
|
484
511
|
(function($) {
|
485
|
-
|
486
|
-
|
487
|
-
|
512
|
+
var getCaretPos, setCaretPos;
|
513
|
+
getCaretPos = function(inputor) {
|
514
|
+
var end, endRange, len, normalizedValue, pos, range, start, textInputRange;
|
515
|
+
if (document.selection) {
|
516
|
+
/*
|
517
|
+
#assume we select "HATE" in the inputor such as textarea -> { }.
|
518
|
+
* start end-point.
|
519
|
+
* /
|
520
|
+
* < I really [HATE] IE > between the brackets is the selection range.
|
521
|
+
* \
|
522
|
+
* end end-point.
|
523
|
+
*/
|
524
|
+
range = document.selection.createRange();
|
525
|
+
pos = 0;
|
526
|
+
if (range && range.parentElement() === inputor) {
|
527
|
+
normalizedValue = inputor.value.replace(/\r\n/g, "\n");
|
528
|
+
/* SOMETIME !!!
|
529
|
+
"/r/n" is counted as two char.
|
530
|
+
one line is two, two will be four. balalala.
|
531
|
+
so we have to using the normalized one's length.;
|
532
|
+
*/
|
533
|
+
len = normalizedValue.length;
|
534
|
+
/*
|
535
|
+
<[ I really HATE IE ]>:
|
536
|
+
the whole content in the inputor will be the textInputRange.
|
537
|
+
*/
|
538
|
+
textInputRange = inputor.createTextRange();
|
539
|
+
/* _here must be the position of bookmark.
|
540
|
+
/
|
541
|
+
<[ I really [HATE] IE ]>
|
542
|
+
[---------->[ ] : this is what moveToBookmark do.
|
543
|
+
< I really [[HATE] IE ]> : here is result.
|
544
|
+
\ two brackets in should be in line.
|
545
|
+
*/
|
546
|
+
textInputRange.moveToBookmark(range.getBookmark());
|
547
|
+
endRange = inputor.createTextRange();
|
548
|
+
/* [--------------------->[] : if set false all end-point goto end.
|
549
|
+
< I really [[HATE] IE []]>
|
550
|
+
*/
|
551
|
+
endRange.collapse(false);
|
552
|
+
/*
|
553
|
+
___VS____
|
554
|
+
/ \
|
555
|
+
< I really [[HATE] IE []]>
|
556
|
+
\_endRange end-point.
|
557
|
+
|
558
|
+
" > -1" mean the start end-point will be the same or right to the end end-point
|
559
|
+
* simplelly, all in the end.
|
560
|
+
*/
|
561
|
+
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
562
|
+
start = end = len;
|
563
|
+
} else {
|
488
564
|
/*
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
*/
|
501
|
-
range = document.selection.createRange();
|
502
|
-
pos = 0;
|
503
|
-
// selection should in the inputor.
|
504
|
-
if (range && range.parentElement() == el) {
|
505
|
-
normalizedValue = el.value.replace(/\r\n/g, "\n");
|
506
|
-
/* SOMETIME !!!
|
507
|
-
*"/r/n" is counted as two char.
|
508
|
-
* one line is two, two will be four. balalala.
|
509
|
-
* so we have to using the normalized one's length.;
|
510
|
-
*/
|
511
|
-
len = normalizedValue.length;
|
512
|
-
|
513
|
-
/*<[ I really HATE IE ]>:
|
514
|
-
* the whole content in the inputor will be the textInputRange.
|
515
|
-
*/
|
516
|
-
textInputRange = el.createTextRange();
|
517
|
-
|
518
|
-
/* _here must be the position of bookmark.
|
519
|
-
* /
|
520
|
-
* <[ I really [HATE] IE ]>
|
521
|
-
* [---------->[ ] : this is what moveToBookmark do.
|
522
|
-
* < I really [[HATE] IE ]> : here is result.
|
523
|
-
* \ two brackets in should be in line.
|
524
|
-
*/
|
525
|
-
textInputRange.moveToBookmark(range.getBookmark());
|
526
|
-
// IE don't want to let "createTextRange" and "collapse" get together. It's so bad
|
527
|
-
endRange = el.createTextRange();
|
528
|
-
|
529
|
-
/* [--------------------->[] : if set false all end-point goto end.
|
530
|
-
* < I really [[HATE] IE []]>
|
531
|
-
*/
|
532
|
-
endRange.collapse(false);
|
533
|
-
/* ___VS____
|
534
|
-
* / \
|
535
|
-
* < I really [[HATE] IE []]>
|
536
|
-
* \_endRange end-point.
|
537
|
-
*
|
538
|
-
* " > -1" mean the start end-point will be the same or right to the end end-point
|
539
|
-
* simplelly, all in the end.
|
540
|
-
*/
|
541
|
-
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
542
|
-
// TextRange object will miss "\r\n". So, we count it ourself.
|
543
|
-
//line_counter = normalizedValue.slice(0, start).split("\n").length -1;
|
544
|
-
start = end = len;
|
545
|
-
} else {
|
546
|
-
/* I really |HATE] IE ]>
|
547
|
-
* <-|
|
548
|
-
* I really[ [HATE] IE ]>
|
549
|
-
* <-[
|
550
|
-
* I reall[y [HATE] IE ]>
|
551
|
-
*
|
552
|
-
* will return how many unit have moved.
|
553
|
-
*/
|
554
|
-
start = -textInputRange.moveStart("character", -len);
|
555
|
-
end = -textInputRange.moveEnd("character", -len);
|
556
|
-
}
|
557
|
-
}
|
558
|
-
} else {
|
559
|
-
start = inputor.selectionStart;
|
560
|
-
}
|
561
|
-
return start;
|
562
|
-
}
|
563
|
-
function setCaretPos(inputor, pos) {
|
564
|
-
if ("selection" in document) { //IE
|
565
|
-
range = inputor.createTextRange();
|
566
|
-
range.move('character',pos);
|
567
|
-
range.select();
|
568
|
-
} else
|
569
|
-
inputor.setSelectionRange(pos,pos);
|
570
|
-
}
|
571
|
-
$.fn.caretPos = function(pos) {
|
572
|
-
var inputor = this[0];
|
573
|
-
inputor.focus();
|
574
|
-
if (pos) {
|
575
|
-
return setCaretPos(inputor,pos);
|
576
|
-
} else {
|
577
|
-
return getCaretPos(inputor);
|
565
|
+
I really |HATE] IE ]>
|
566
|
+
<-|
|
567
|
+
I really[ [HATE] IE ]>
|
568
|
+
<-[
|
569
|
+
I reall[y [HATE] IE ]>
|
570
|
+
|
571
|
+
will return how many unit have moved.
|
572
|
+
*/
|
573
|
+
start = -textInputRange.moveStart("character", -len);
|
574
|
+
end = -textInputRange.moveEnd("character", -len);
|
575
|
+
}
|
578
576
|
}
|
579
|
-
|
580
|
-
|
577
|
+
} else {
|
578
|
+
start = inputor.selectionStart;
|
579
|
+
}
|
580
|
+
return start;
|
581
|
+
};
|
582
|
+
setCaretPos = function(inputor, pos) {
|
583
|
+
var range;
|
584
|
+
if (document.selection) {
|
585
|
+
range = inputor.createTextRange();
|
586
|
+
range.move("character", pos);
|
587
|
+
return range.select();
|
588
|
+
} else {
|
589
|
+
return inputor.setSelectionRange(pos, pos);
|
590
|
+
}
|
591
|
+
};
|
592
|
+
return $.fn.caretPos = function(pos) {
|
593
|
+
var inputor;
|
594
|
+
inputor = this[0];
|
595
|
+
inputor.focus();
|
596
|
+
if (pos) {
|
597
|
+
return setCaretPos(inputor, pos);
|
598
|
+
} else {
|
599
|
+
return getCaretPos(inputor);
|
600
|
+
}
|
601
|
+
};
|
602
|
+
})(window.jQuery);
|
603
|
+
|
@@ -10,12 +10,6 @@
|
|
10
10
|
box-shadow: 0 0 5px rgba(0,0,0,0.1);
|
11
11
|
min-width: 120px;
|
12
12
|
}
|
13
|
-
#at-view span#title {
|
14
|
-
font-size:13px;
|
15
|
-
color: gray;
|
16
|
-
padding: 3px 5px;
|
17
|
-
cursor: default;
|
18
|
-
}
|
19
13
|
|
20
14
|
#at-view .cur {
|
21
15
|
background: #3366FF;
|
@@ -24,6 +18,13 @@
|
|
24
18
|
#at-view .cur small {
|
25
19
|
color: white;
|
26
20
|
}
|
21
|
+
#at-view strong {
|
22
|
+
color: #3366FF;
|
23
|
+
}
|
24
|
+
#at-view .cur strong {
|
25
|
+
color: white;
|
26
|
+
font:bold;
|
27
|
+
}
|
27
28
|
#at-view ul {
|
28
29
|
/* width: 100px; */
|
29
30
|
list-style:none;
|
@@ -34,7 +35,7 @@
|
|
34
35
|
display: block;
|
35
36
|
padding: 5px 10px;
|
36
37
|
border-bottom: 1px solid #DDD;
|
37
|
-
|
38
|
+
cursor: pointer;
|
38
39
|
/* border-top: 1px solid #C8C8C8; */
|
39
40
|
}
|
40
41
|
#at-view small {
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jquery-atwho-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-04-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|