jquerypp-rails 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +24 -0
- data/Rakefile +2 -0
- data/jquerypp-rails.gemspec +20 -0
- data/lib/jquerypp/generators/jquerypp/install/install_generator.rb +49 -0
- data/lib/jquerypp/rails/engine.rb +8 -0
- data/lib/jquerypp/rails/version.rb +6 -0
- data/lib/jquerypp/rails.rb +8 -0
- data/lib/jquerypp-rails.rb +1 -0
- data/vendor/assets/javascripts/jquerypp.js +5419 -0
- data/vendor/assets/javascripts/lib/jquery.animate.js +326 -0
- data/vendor/assets/javascripts/lib/jquery.compare.js +75 -0
- data/vendor/assets/javascripts/lib/jquery.cookie.js +118 -0
- data/vendor/assets/javascripts/lib/jquery.dimensions.js +191 -0
- data/vendor/assets/javascripts/lib/jquery.event.default.js +115 -0
- data/vendor/assets/javascripts/lib/jquery.event.destroyed.js +23 -0
- data/vendor/assets/javascripts/lib/jquery.event.drag.js +727 -0
- data/vendor/assets/javascripts/lib/jquery.event.drop.js +457 -0
- data/vendor/assets/javascripts/lib/jquery.event.fastfix.js +95 -0
- data/vendor/assets/javascripts/lib/jquery.event.hover.js +266 -0
- data/vendor/assets/javascripts/lib/jquery.event.key.js +156 -0
- data/vendor/assets/javascripts/lib/jquery.event.livehack.js +174 -0
- data/vendor/assets/javascripts/lib/jquery.event.pause.js +92 -0
- data/vendor/assets/javascripts/lib/jquery.event.resize.js +47 -0
- data/vendor/assets/javascripts/lib/jquery.event.swipe.js +133 -0
- data/vendor/assets/javascripts/lib/jquery.fills.js +249 -0
- data/vendor/assets/javascripts/lib/jquery.form_params.js +167 -0
- data/vendor/assets/javascripts/lib/jquery.lang.json.js +196 -0
- data/vendor/assets/javascripts/lib/jquery.lang.vector.js +214 -0
- data/vendor/assets/javascripts/lib/jquery.range.js +861 -0
- data/vendor/assets/javascripts/lib/jquery.selection.js +232 -0
- data/vendor/assets/javascripts/lib/jquery.styles.js +103 -0
- data/vendor/assets/javascripts/lib/jquery.within.js +94 -0
- metadata +81 -0
@@ -0,0 +1,861 @@
|
|
1
|
+
// Dependencies:
|
2
|
+
//
|
3
|
+
// - jquery.range.js
|
4
|
+
// - jquery.compare.js
|
5
|
+
|
6
|
+
(function($){
|
7
|
+
|
8
|
+
$.fn.range =
|
9
|
+
/**
|
10
|
+
* @function jQuery.fn.range
|
11
|
+
* @parent jQuery.Range
|
12
|
+
*
|
13
|
+
* `$.fn.range` returns a new [jQuery.Range] instance for the first selected element.
|
14
|
+
*
|
15
|
+
* $('#content').range() //-> range
|
16
|
+
*
|
17
|
+
* @return {$.Range} A $.Range instance for the selected element
|
18
|
+
*/
|
19
|
+
function(){
|
20
|
+
return $.Range(this[0])
|
21
|
+
}
|
22
|
+
|
23
|
+
var convertType = function(type){
|
24
|
+
return type.replace(/([a-z])([a-z]+)/gi, function(all,first, next){
|
25
|
+
return first+next.toLowerCase()
|
26
|
+
}).replace(/_/g,"");
|
27
|
+
},
|
28
|
+
// reverses things like START_TO_END into END_TO_START
|
29
|
+
reverse = function(type){
|
30
|
+
return type.replace(/^([a-z]+)_TO_([a-z]+)/i, function(all, first, last){
|
31
|
+
return last+"_TO_"+first;
|
32
|
+
});
|
33
|
+
},
|
34
|
+
getWindow = function( element ) {
|
35
|
+
return element ? element.ownerDocument.defaultView || element.ownerDocument.parentWindow : window
|
36
|
+
},
|
37
|
+
bisect = function(el, start, end){
|
38
|
+
//split the start and end ... figure out who is touching ...
|
39
|
+
if(end-start == 1){
|
40
|
+
return
|
41
|
+
}
|
42
|
+
},
|
43
|
+
support = {};
|
44
|
+
/**
|
45
|
+
* @Class jQuery.Range
|
46
|
+
* @parent jQuery.Range
|
47
|
+
*
|
48
|
+
* Depending on the object passed, the selected text will be different.
|
49
|
+
*
|
50
|
+
* @param {TextRange|HTMLElement|Point} [range] An object specifiying a
|
51
|
+
* range. Depending on the object, the selected text will be different. $.Range supports the
|
52
|
+
* following types
|
53
|
+
*
|
54
|
+
* - __undefined or null__ - returns a range with nothing selected
|
55
|
+
* - __HTMLElement__ - returns a range with the node's text selected
|
56
|
+
* - __Point__ - returns a range at the point on the screen. The point can be specified like:
|
57
|
+
*
|
58
|
+
* //client coordinates
|
59
|
+
* {clientX: 200, clientY: 300}
|
60
|
+
*
|
61
|
+
* //page coordinates
|
62
|
+
* {pageX: 200, pageY: 300}
|
63
|
+
* {top: 200, left: 300}
|
64
|
+
*
|
65
|
+
* - __TextRange__ a raw text range object.
|
66
|
+
*/
|
67
|
+
$.Range = function(range){
|
68
|
+
// If it's called w/o new, call it with new!
|
69
|
+
if(this.constructor !== $.Range){
|
70
|
+
return new $.Range(range);
|
71
|
+
}
|
72
|
+
// If we are passed a jQuery-wrapped element, get the raw element
|
73
|
+
if(range && range.jquery){
|
74
|
+
range = range[0];
|
75
|
+
}
|
76
|
+
// If we have an element, or nothing
|
77
|
+
if(!range || range.nodeType){
|
78
|
+
// create a range
|
79
|
+
this.win = getWindow(range)
|
80
|
+
if(this.win.document.createRange){
|
81
|
+
this.range = this.win.document.createRange()
|
82
|
+
}else{
|
83
|
+
this.range = this.win.document.body.createTextRange()
|
84
|
+
}
|
85
|
+
// if we have an element, make the range select it
|
86
|
+
if(range){
|
87
|
+
this.select(range)
|
88
|
+
}
|
89
|
+
}
|
90
|
+
// if we are given a point
|
91
|
+
else if (range.clientX != null || range.pageX != null || range.left != null) {
|
92
|
+
this.moveToPoint(range);
|
93
|
+
}
|
94
|
+
// if we are given a touch event
|
95
|
+
else if (range.originalEvent && range.originalEvent.touches && range.originalEvent.touches.length) {
|
96
|
+
this.moveToPoint(range.originalEvent.touches[0])
|
97
|
+
|
98
|
+
}
|
99
|
+
// if we are a normal event
|
100
|
+
else if (range.originalEvent && range.originalEvent.changedTouches && range.originalEvent.changedTouches.length) {
|
101
|
+
this.moveToPoint(range.originalEvent.changedTouches[0])
|
102
|
+
}
|
103
|
+
// given a TextRange or something else?
|
104
|
+
else {
|
105
|
+
this.range = range;
|
106
|
+
}
|
107
|
+
};
|
108
|
+
/**
|
109
|
+
* @static
|
110
|
+
*/
|
111
|
+
$.Range.
|
112
|
+
/**
|
113
|
+
* `$.Range.current([element])` returns the currently selected range
|
114
|
+
* (using [window.getSelection](https://developer.mozilla.org/en/nsISelection)).
|
115
|
+
*
|
116
|
+
* var range = $.Range.current()
|
117
|
+
* range.start().offset // -> selection start offset
|
118
|
+
* range.end().offset // -> selection end offset
|
119
|
+
*
|
120
|
+
* @param {HTMLElement} [el] an optional element used to get selection for a given window.
|
121
|
+
* @return {jQuery.Range} The range instance.
|
122
|
+
*/
|
123
|
+
current = function(el){
|
124
|
+
var win = getWindow(el),
|
125
|
+
selection;
|
126
|
+
if(win.getSelection){
|
127
|
+
// If we can get the selection
|
128
|
+
selection = win.getSelection()
|
129
|
+
return new $.Range( selection.rangeCount ? selection.getRangeAt(0) : win.document.createRange())
|
130
|
+
} else {
|
131
|
+
// Otherwise use document.selection
|
132
|
+
return new $.Range( win.document.selection.createRange() );
|
133
|
+
}
|
134
|
+
};
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
|
139
|
+
$.extend($.Range.prototype,
|
140
|
+
/** @prototype **/
|
141
|
+
{
|
142
|
+
/**
|
143
|
+
* `range.moveToPoint(point)` moves the range end and start position to a specific point.
|
144
|
+
* A point can be specified like:
|
145
|
+
*
|
146
|
+
* //client coordinates
|
147
|
+
* {clientX: 200, clientY: 300}
|
148
|
+
*
|
149
|
+
* //page coordinates
|
150
|
+
* {pageX: 200, pageY: 300}
|
151
|
+
* {top: 200, left: 300}
|
152
|
+
*
|
153
|
+
* @param point The point to move the range to
|
154
|
+
* @return {$.Range}
|
155
|
+
*/
|
156
|
+
moveToPoint : function(point){
|
157
|
+
var clientX = point.clientX, clientY = point.clientY
|
158
|
+
if(!clientX){
|
159
|
+
var off = scrollOffset();
|
160
|
+
clientX = (point.pageX || point.left || 0 ) - off.left;
|
161
|
+
clientY = (point.pageY || point.top || 0 ) - off.top;
|
162
|
+
}
|
163
|
+
if(support.moveToPoint){
|
164
|
+
this.range = $.Range().range
|
165
|
+
this.range.moveToPoint(clientX, clientY);
|
166
|
+
return this;
|
167
|
+
}
|
168
|
+
|
169
|
+
|
170
|
+
// it's some text node in this range ...
|
171
|
+
var parent = document.elementFromPoint(clientX, clientY);
|
172
|
+
|
173
|
+
//typically it will be 'on' text
|
174
|
+
for(var n=0; n < parent.childNodes.length; n++){
|
175
|
+
var node = parent.childNodes[n];
|
176
|
+
if(node.nodeType === 3 || node.nodeType === 4){
|
177
|
+
var range = $.Range(node),
|
178
|
+
length = range.toString().length;
|
179
|
+
|
180
|
+
|
181
|
+
// now lets start moving the end until the boundingRect is within our range
|
182
|
+
for(var i = 1; i < length+1; i++){
|
183
|
+
var rect = range.end(i).rect();
|
184
|
+
if(rect.left <= clientX && rect.left+rect.width >= clientX &&
|
185
|
+
rect.top <= clientY && rect.top+rect.height >= clientY ){
|
186
|
+
range.start(i-1);
|
187
|
+
this.range = range.range;
|
188
|
+
return this;
|
189
|
+
}
|
190
|
+
}
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
// if not 'on' text, recursively go through and find out when we shift to next
|
195
|
+
// 'line'
|
196
|
+
var previous;
|
197
|
+
iterate(parent.childNodes, function(textNode){
|
198
|
+
var range = $.Range(textNode);
|
199
|
+
if(range.rect().top > point.clientY){
|
200
|
+
return false;
|
201
|
+
}else{
|
202
|
+
previous = range;
|
203
|
+
}
|
204
|
+
});
|
205
|
+
|
206
|
+
if(previous){
|
207
|
+
previous.start(previous.toString().length);
|
208
|
+
this.range = previous.range;
|
209
|
+
}else{
|
210
|
+
this.range = $.Range(parent).range
|
211
|
+
}
|
212
|
+
},
|
213
|
+
|
214
|
+
window : function(){
|
215
|
+
return this.win || window;
|
216
|
+
},
|
217
|
+
/**
|
218
|
+
* `range.overlaps([elRange])` returns `true` if any portion of these two ranges overlap.
|
219
|
+
*
|
220
|
+
* var foo = document.getElementById('foo');
|
221
|
+
*
|
222
|
+
* $.Range(foo.childNodes[0]).overlaps(foo.childNodes[1]) //-> false
|
223
|
+
*
|
224
|
+
* @param {jQuery.Range} elRange The range to compare
|
225
|
+
* @return {Boolean} true if part of the ranges overlap, false if otherwise.
|
226
|
+
*/
|
227
|
+
overlaps : function(elRange){
|
228
|
+
if(elRange.nodeType){
|
229
|
+
elRange = $.Range(elRange).select(elRange);
|
230
|
+
}
|
231
|
+
//if the start is within the element ...
|
232
|
+
var startToStart = this.compare("START_TO_START", elRange),
|
233
|
+
endToEnd = this.compare("END_TO_END", elRange)
|
234
|
+
|
235
|
+
// if we wrap elRange
|
236
|
+
if(startToStart <=0 && endToEnd >=0){
|
237
|
+
return true;
|
238
|
+
}
|
239
|
+
// if our start is inside of it
|
240
|
+
if( startToStart >= 0 &&
|
241
|
+
this.compare("START_TO_END", elRange) <= 0 ) {
|
242
|
+
return true;
|
243
|
+
}
|
244
|
+
// if our end is inside of elRange
|
245
|
+
if(this.compare("END_TO_START", elRange) >= 0 &&
|
246
|
+
endToEnd <= 0 ) {
|
247
|
+
return true;
|
248
|
+
}
|
249
|
+
return false;
|
250
|
+
},
|
251
|
+
/**
|
252
|
+
* `range.collapse([toStart])` collapses a range to one of its boundary points.
|
253
|
+
* See [range.collapse](https://developer.mozilla.org/en/DOM/range.collapse).
|
254
|
+
*
|
255
|
+
* $('#foo').range().collapse()
|
256
|
+
*
|
257
|
+
* @param {Boolean} [toStart] true if to the start of the range, false if to the
|
258
|
+
* end. Defaults to false.
|
259
|
+
* @return {jQuery.Range} returns the range for chaining.
|
260
|
+
*/
|
261
|
+
collapse : function(toStart){
|
262
|
+
this.range.collapse(toStart === undefined ? true : toStart);
|
263
|
+
return this;
|
264
|
+
},
|
265
|
+
/**
|
266
|
+
* `range.toString()` returns the text of the range.
|
267
|
+
*
|
268
|
+
* currentText = $.Range.current().toString()
|
269
|
+
*
|
270
|
+
* @return {String} The text content of this range
|
271
|
+
*/
|
272
|
+
toString : function(){
|
273
|
+
return typeof this.range.text == "string" ? this.range.text : this.range.toString();
|
274
|
+
},
|
275
|
+
/**
|
276
|
+
* `range.start([start])` gets or sets the start of the range.
|
277
|
+
*
|
278
|
+
* If a value is not provided, start returns the range's starting container and offset like:
|
279
|
+
*
|
280
|
+
* $('#foo').range().start()
|
281
|
+
* //-> {container: fooElement, offset: 0 }
|
282
|
+
*
|
283
|
+
* If a set value is provided, it can set the range. The start of the range is set differently
|
284
|
+
* depending on the type of set value:
|
285
|
+
*
|
286
|
+
* - __Object__ - an object with the new starting container and offset like
|
287
|
+
*
|
288
|
+
* $.Range().start({container: $('#foo')[0], offset: 20})
|
289
|
+
*
|
290
|
+
* - __Number__ - the new offset value. The container is kept the same.
|
291
|
+
*
|
292
|
+
* - __String__ - adjusts the offset by converting the string offset to a number and adding it to the current
|
293
|
+
* offset. For example, the following moves the offset forward four characters:
|
294
|
+
*
|
295
|
+
* $('#foo').range().start("+4")
|
296
|
+
*
|
297
|
+
* Note that `start` can return a text node. To get the containing element use this:
|
298
|
+
*
|
299
|
+
* var startNode = range.start().container;
|
300
|
+
* if( startNode.nodeType === Node.TEXT_NODE ||
|
301
|
+
* startNode.nodeType === Node.CDATA_SECTION_NODE ) {
|
302
|
+
* startNode = startNode.parentNode;
|
303
|
+
* }
|
304
|
+
* $(startNode).addClass('highlight');
|
305
|
+
*
|
306
|
+
* @param {Object|String|Number} [set] a set value if setting the start of the range or nothing if reading it.
|
307
|
+
* @return {jQuery.Range|Object} if setting the start, the range is returned for chaining, otherwise, the
|
308
|
+
* start offset and container are returned.
|
309
|
+
*/
|
310
|
+
start : function(set){
|
311
|
+
// return start
|
312
|
+
if(set === undefined){
|
313
|
+
if(this.range.startContainer){
|
314
|
+
return {
|
315
|
+
container : this.range.startContainer,
|
316
|
+
offset : this.range.startOffset
|
317
|
+
}
|
318
|
+
}else{
|
319
|
+
// Get the start parent element
|
320
|
+
var start = this.clone().collapse().parent();
|
321
|
+
// used to get the start element offset
|
322
|
+
var startRange = $.Range(start).select(start).collapse();
|
323
|
+
startRange.move("END_TO_START", this);
|
324
|
+
return {
|
325
|
+
container : start,
|
326
|
+
offset : startRange.toString().length
|
327
|
+
}
|
328
|
+
}
|
329
|
+
} else {
|
330
|
+
if (this.range.setStart) {
|
331
|
+
// supports setStart
|
332
|
+
if(typeof set == 'number'){
|
333
|
+
this.range.setStart(this.range.startContainer, set)
|
334
|
+
} else if(typeof set == 'string') {
|
335
|
+
var res = callMove(this.range.startContainer, this.range.startOffset, parseInt(set,10))
|
336
|
+
this.range.setStart(res.node, res.offset );
|
337
|
+
} else {
|
338
|
+
this.range.setStart(set.container, set.offset)
|
339
|
+
}
|
340
|
+
} else {
|
341
|
+
if(typeof set == "string"){
|
342
|
+
this.range.moveStart('character', parseInt(set,10))
|
343
|
+
} else {
|
344
|
+
// get the current end container
|
345
|
+
var container = this.start().container,
|
346
|
+
offset
|
347
|
+
if(typeof set == "number") {
|
348
|
+
offset = set
|
349
|
+
} else {
|
350
|
+
container = set.container
|
351
|
+
offset = set.offset
|
352
|
+
}
|
353
|
+
var newPoint = $.Range(container).collapse();
|
354
|
+
//move it over offset characters
|
355
|
+
newPoint.range.move(offset);
|
356
|
+
this.move("START_TO_START",newPoint);
|
357
|
+
}
|
358
|
+
}
|
359
|
+
return this;
|
360
|
+
}
|
361
|
+
|
362
|
+
|
363
|
+
},
|
364
|
+
/**
|
365
|
+
* `range.end([end])` gets or sets the end of the range.
|
366
|
+
* It takes similar options as [jQuery.Range::start start]:
|
367
|
+
*
|
368
|
+
* - __Object__ - an object with the new end container and offset like
|
369
|
+
*
|
370
|
+
* $.Range().end({container: $('#foo')[0], offset: 20})
|
371
|
+
*
|
372
|
+
* - __Number__ - the new offset value. The container is kept the same.
|
373
|
+
*
|
374
|
+
* - __String__ - adjusts the offset by converting the string offset to a number and adding it to the current
|
375
|
+
* offset. For example, the following moves the offset forward four characters:
|
376
|
+
*
|
377
|
+
* $('#foo').range().end("+4")
|
378
|
+
*
|
379
|
+
* Note that `end` can return a text node. To get the containing element use this:
|
380
|
+
*
|
381
|
+
* var startNode = range.end().container;
|
382
|
+
* if( startNode.nodeType === Node.TEXT_NODE ||
|
383
|
+
* startNode.nodeType === Node.CDATA_SECTION_NODE ) {
|
384
|
+
* startNode = startNode.parentNode;
|
385
|
+
* }
|
386
|
+
* $(startNode).addClass('highlight');
|
387
|
+
*
|
388
|
+
* @param {Object|String|Number} [set] a set value if setting the end of the range or nothing if reading it.
|
389
|
+
*/
|
390
|
+
end : function(set){
|
391
|
+
// read end
|
392
|
+
if (set === undefined) {
|
393
|
+
if (this.range.startContainer) {
|
394
|
+
return {
|
395
|
+
container: this.range.endContainer,
|
396
|
+
offset: this.range.endOffset
|
397
|
+
}
|
398
|
+
}
|
399
|
+
else {
|
400
|
+
var
|
401
|
+
// Get the end parent element
|
402
|
+
end = this.clone().collapse(false).parent(),
|
403
|
+
// used to get the end elements offset
|
404
|
+
endRange = $.Range(end).select(end).collapse();
|
405
|
+
endRange.move("END_TO_END", this);
|
406
|
+
return {
|
407
|
+
container: end,
|
408
|
+
offset: endRange.toString().length
|
409
|
+
}
|
410
|
+
}
|
411
|
+
} else {
|
412
|
+
if (this.range.setEnd) {
|
413
|
+
if(typeof set == 'number'){
|
414
|
+
this.range.setEnd(this.range.endContainer, set)
|
415
|
+
} else if(typeof set == 'string') {
|
416
|
+
var res = callMove(this.range.endContainer, this.range.endOffset, parseInt(set,10))
|
417
|
+
this.range.setEnd(res.node, res.offset );
|
418
|
+
} else {
|
419
|
+
this.range.setEnd(set.container, set.offset)
|
420
|
+
}
|
421
|
+
} else {
|
422
|
+
if(typeof set == "string"){
|
423
|
+
this.range.moveEnd('character', parseInt(set,10));
|
424
|
+
} else {
|
425
|
+
// get the current end container
|
426
|
+
var container = this.end().container,
|
427
|
+
offset
|
428
|
+
if(typeof set == "number") {
|
429
|
+
offset = set
|
430
|
+
} else {
|
431
|
+
container = set.container
|
432
|
+
offset = set.offset
|
433
|
+
}
|
434
|
+
var newPoint = $.Range(container).collapse();
|
435
|
+
//move it over offset characters
|
436
|
+
newPoint.range.move(offset);
|
437
|
+
this.move("END_TO_START",newPoint);
|
438
|
+
}
|
439
|
+
}
|
440
|
+
return this;
|
441
|
+
}
|
442
|
+
},
|
443
|
+
/**
|
444
|
+
* `range.parent()` returns the most common ancestor element of
|
445
|
+
* the endpoints in the range. This will return a text element if the range is
|
446
|
+
* within a text element. In this case, to get the containing element use this:
|
447
|
+
*
|
448
|
+
* var parent = range.parent();
|
449
|
+
* if( parent.nodeType === Node.TEXT_NODE ||
|
450
|
+
* parent.nodeType === Node.CDATA_SECTION_NODE ) {
|
451
|
+
* parent = startNode.parentNode;
|
452
|
+
* }
|
453
|
+
* $(parent).addClass('highlight');
|
454
|
+
*
|
455
|
+
* @return {HTMLNode} the TextNode or HTMLElement
|
456
|
+
* that fully contains the range
|
457
|
+
*/
|
458
|
+
parent : function(){
|
459
|
+
if(this.range.commonAncestorContainer){
|
460
|
+
return this.range.commonAncestorContainer;
|
461
|
+
} else {
|
462
|
+
|
463
|
+
var parentElement = this.range.parentElement(),
|
464
|
+
range = this.range;
|
465
|
+
|
466
|
+
// IE's parentElement will always give an element, we want text ranges
|
467
|
+
iterate(parentElement.childNodes, function(txtNode){
|
468
|
+
if($.Range(txtNode).range.inRange( range ) ){
|
469
|
+
// swap out the parentElement
|
470
|
+
parentElement = txtNode;
|
471
|
+
return false;
|
472
|
+
}
|
473
|
+
});
|
474
|
+
|
475
|
+
return parentElement;
|
476
|
+
}
|
477
|
+
},
|
478
|
+
/**
|
479
|
+
* `range.rect([from])` returns the bounding rectangle of this range.
|
480
|
+
*
|
481
|
+
* @param {String} [from] - where the coordinates should be
|
482
|
+
* positioned from. By default, coordinates are given from the client viewport.
|
483
|
+
* But if 'page' is given, they are provided relative to the page.
|
484
|
+
*
|
485
|
+
* @return {TextRectangle} - The client rects.
|
486
|
+
*/
|
487
|
+
rect : function(from){
|
488
|
+
var rect = this.range.getBoundingClientRect();
|
489
|
+
// for some reason in webkit this gets a better value
|
490
|
+
if(!rect.height && !rect.width){
|
491
|
+
rect = this.range.getClientRects()[0]
|
492
|
+
}
|
493
|
+
if(from === 'page'){
|
494
|
+
// Add the scroll offset
|
495
|
+
var off = scrollOffset();
|
496
|
+
rect = $.extend({}, rect);
|
497
|
+
rect.top += off.top;
|
498
|
+
rect.left += off.left;
|
499
|
+
}
|
500
|
+
return rect;
|
501
|
+
},
|
502
|
+
/**
|
503
|
+
* `range.rects(from)` returns the client rects.
|
504
|
+
*
|
505
|
+
* @param {String} [from] how the rects coordinates should be given (viewport or page). Provide 'page' for
|
506
|
+
* rect coordinates from the page.
|
507
|
+
* @return {Array} The client rects
|
508
|
+
*/
|
509
|
+
rects : function(from){
|
510
|
+
// order rects by size
|
511
|
+
var rects = $.map($.makeArray( this.range.getClientRects() ).sort(function(rect1, rect2){
|
512
|
+
return rect2.width*rect2.height - rect1.width*rect1.height;
|
513
|
+
}), function(rect){
|
514
|
+
return $.extend({}, rect)
|
515
|
+
}),
|
516
|
+
i=0,j,
|
517
|
+
len = rects.length;
|
518
|
+
|
519
|
+
// safari returns overlapping client rects
|
520
|
+
//
|
521
|
+
// - big rects can contain 2 smaller rects
|
522
|
+
// - some rects can contain 0 - width rects
|
523
|
+
// - we don't want these 0 width rects
|
524
|
+
while(i < rects.length){
|
525
|
+
var cur = rects[i],
|
526
|
+
found = false;
|
527
|
+
|
528
|
+
j = i+1;
|
529
|
+
while( j < rects.length ){
|
530
|
+
if( withinRect( cur, rects[j] ) ) {
|
531
|
+
if(!rects[j].width){
|
532
|
+
rects.splice(j,1)
|
533
|
+
} else {
|
534
|
+
found = rects[j];
|
535
|
+
break;
|
536
|
+
}
|
537
|
+
} else {
|
538
|
+
j++;
|
539
|
+
}
|
540
|
+
}
|
541
|
+
|
542
|
+
|
543
|
+
if(found){
|
544
|
+
rects.splice(i,1)
|
545
|
+
}else{
|
546
|
+
i++;
|
547
|
+
}
|
548
|
+
|
549
|
+
}
|
550
|
+
// safari will be return overlapping ranges ...
|
551
|
+
if(from == 'page'){
|
552
|
+
var off = scrollOffset();
|
553
|
+
return $.each(rects, function(ith, item){
|
554
|
+
item.top += off.top;
|
555
|
+
item.left += off.left;
|
556
|
+
})
|
557
|
+
}
|
558
|
+
|
559
|
+
|
560
|
+
return rects;
|
561
|
+
}
|
562
|
+
|
563
|
+
});
|
564
|
+
(function(){
|
565
|
+
//method branching ....
|
566
|
+
var fn = $.Range.prototype,
|
567
|
+
range = $.Range().range;
|
568
|
+
|
569
|
+
/**
|
570
|
+
* @function compare
|
571
|
+
*
|
572
|
+
* `range.compare(type, compareRange)` compares one range to another range.
|
573
|
+
*
|
574
|
+
* ## Example
|
575
|
+
*
|
576
|
+
* // compare the highlight element's start position
|
577
|
+
* // to the start of the current range
|
578
|
+
* $('#highlight')
|
579
|
+
* .range()
|
580
|
+
* .compare('START_TO_START', $.Range.current())
|
581
|
+
*
|
582
|
+
*
|
583
|
+
*
|
584
|
+
* @param {String} type Specifies the boundary of the
|
585
|
+
* range and the <code>compareRange</code> to compare.
|
586
|
+
*
|
587
|
+
* - `"START_TO_START"` - the start of the range and the start of compareRange
|
588
|
+
* - `"START_TO_END"` - the start of the range and the end of compareRange
|
589
|
+
* - `"END_TO_END"` - the end of the range and the end of compareRange
|
590
|
+
* - `"END_TO_START"` - the end of the range and the start of compareRange
|
591
|
+
*
|
592
|
+
* @param {$.Range} compareRange The other range
|
593
|
+
* to compare against.
|
594
|
+
* @return {Number} a number indicating if the range
|
595
|
+
* boundary is before,
|
596
|
+
* after, or equal to <code>compareRange</code>'s
|
597
|
+
* boundary where:
|
598
|
+
*
|
599
|
+
* - -1 - the range boundary comes before the compareRange boundary
|
600
|
+
* - 0 - the boundaries are equal
|
601
|
+
* - 1 - the range boundary comes after the compareRange boundary
|
602
|
+
*/
|
603
|
+
fn.compare = range.compareBoundaryPoints ?
|
604
|
+
function(type, range){
|
605
|
+
return this.range.compareBoundaryPoints(this.window().Range[reverse( type )], range.range)
|
606
|
+
}:
|
607
|
+
function(type, range){
|
608
|
+
return this.range.compareEndPoints(convertType(type), range.range)
|
609
|
+
}
|
610
|
+
|
611
|
+
/**
|
612
|
+
* @function move
|
613
|
+
*
|
614
|
+
* `range.move([referenceRange])` moves the endpoints of a range relative to another range.
|
615
|
+
*
|
616
|
+
* // Move the current selection's end to the
|
617
|
+
* // end of the #highlight element
|
618
|
+
* $.Range.current().move('END_TO_END',
|
619
|
+
* $('#highlight').range() )
|
620
|
+
*
|
621
|
+
*
|
622
|
+
* @param {String} type a string indicating the ranges boundary point
|
623
|
+
* to move to which referenceRange boundary point where:
|
624
|
+
*
|
625
|
+
* - `"START_TO_START"` - the start of the range moves to the start of referenceRange
|
626
|
+
* - `"START\_TO\_END"` - the start of the range move to the end of referenceRange
|
627
|
+
* - `"END_TO_END"` - the end of the range moves to the end of referenceRange
|
628
|
+
* - `"END_TO_START"` - the end of the range moves to the start of referenceRange
|
629
|
+
*
|
630
|
+
* @param {jQuery.Range} referenceRange
|
631
|
+
* @return {jQuery.Range} the original range for chaining
|
632
|
+
*/
|
633
|
+
fn.move = range.setStart ?
|
634
|
+
function(type, range){
|
635
|
+
|
636
|
+
var rangesRange = range.range;
|
637
|
+
switch(type){
|
638
|
+
case "START_TO_END" :
|
639
|
+
this.range.setStart(rangesRange.endContainer, rangesRange.endOffset)
|
640
|
+
break;
|
641
|
+
case "START_TO_START" :
|
642
|
+
this.range.setStart(rangesRange.startContainer, rangesRange.startOffset)
|
643
|
+
break;
|
644
|
+
case "END_TO_END" :
|
645
|
+
this.range.setEnd(rangesRange.endContainer, rangesRange.endOffset)
|
646
|
+
break;
|
647
|
+
case "END_TO_START" :
|
648
|
+
this.range.setEnd(rangesRange.startContainer, rangesRange.startOffset)
|
649
|
+
break;
|
650
|
+
}
|
651
|
+
|
652
|
+
return this;
|
653
|
+
}:
|
654
|
+
function(type, range){
|
655
|
+
this.range.setEndPoint(convertType(type), range.range)
|
656
|
+
return this;
|
657
|
+
};
|
658
|
+
var cloneFunc = range.cloneRange ? "cloneRange" : "duplicate",
|
659
|
+
selectFunc = range.selectNodeContents ? "selectNodeContents" : "moveToElementText";
|
660
|
+
|
661
|
+
fn.
|
662
|
+
/**
|
663
|
+
* `range.clone()` clones the range and returns a new $.Range
|
664
|
+
* object:
|
665
|
+
*
|
666
|
+
* var range = new $.Range(document.getElementById('text'));
|
667
|
+
* var newRange = range.clone();
|
668
|
+
* range.start('+2');
|
669
|
+
* range.select();
|
670
|
+
*
|
671
|
+
* @return {jQuery.Range} returns the range as a $.Range.
|
672
|
+
*/
|
673
|
+
clone = function(){
|
674
|
+
return $.Range( this.range[cloneFunc]() );
|
675
|
+
};
|
676
|
+
|
677
|
+
fn.
|
678
|
+
/**
|
679
|
+
* @function
|
680
|
+
*
|
681
|
+
* `range.select([el])` selects an element with this range. If nothing
|
682
|
+
* is provided, makes the current range appear as if the user has selected it.
|
683
|
+
*
|
684
|
+
* This works with text nodes. For example with:
|
685
|
+
*
|
686
|
+
* <div id="text">This is a text</div>
|
687
|
+
*
|
688
|
+
* $.Range can select `is a` like this:
|
689
|
+
*
|
690
|
+
* var range = new $.Range(document.getElementById('text'));
|
691
|
+
* range.start('+5');
|
692
|
+
* range.end('-5');
|
693
|
+
* range.select();
|
694
|
+
*
|
695
|
+
* @param {HTMLElement} [el] The element in which this range should be selected
|
696
|
+
* @return {jQuery.Range} the range for chaining.
|
697
|
+
*/
|
698
|
+
select = range.selectNodeContents ? function(el){
|
699
|
+
if(!el){
|
700
|
+
var selection = this.window().getSelection();
|
701
|
+
selection.removeAllRanges();
|
702
|
+
selection.addRange(this.range);
|
703
|
+
}else {
|
704
|
+
this.range.selectNodeContents(el);
|
705
|
+
}
|
706
|
+
return this;
|
707
|
+
} : function(el){
|
708
|
+
if(!el){
|
709
|
+
this.range.select()
|
710
|
+
} else if(el.nodeType === 3){
|
711
|
+
//select this node in the element ...
|
712
|
+
var parent = el.parentNode,
|
713
|
+
start = 0,
|
714
|
+
end;
|
715
|
+
iterate(parent.childNodes, function(txtNode){
|
716
|
+
if(txtNode === el){
|
717
|
+
end = start + txtNode.nodeValue.length;
|
718
|
+
return false;
|
719
|
+
} else {
|
720
|
+
start = start + txtNode.nodeValue.length
|
721
|
+
}
|
722
|
+
})
|
723
|
+
this.range.moveToElementText(parent);
|
724
|
+
|
725
|
+
this.range.moveEnd('character', end - this.range.text.length)
|
726
|
+
this.range.moveStart('character', start);
|
727
|
+
} else {
|
728
|
+
this.range.moveToElementText(el);
|
729
|
+
}
|
730
|
+
return this;
|
731
|
+
};
|
732
|
+
|
733
|
+
})();
|
734
|
+
|
735
|
+
|
736
|
+
// helpers -----------------
|
737
|
+
|
738
|
+
// iterates through a list of elements, calls cb on every text node
|
739
|
+
// if cb returns false, exits the iteration
|
740
|
+
var iterate = function(elems, cb){
|
741
|
+
var elem, start;
|
742
|
+
for (var i = 0; elems[i]; i++) {
|
743
|
+
elem = elems[i];
|
744
|
+
// Get the text from text nodes and CDATA nodes
|
745
|
+
if (elem.nodeType === 3 || elem.nodeType === 4) {
|
746
|
+
if (cb(elem) === false) {
|
747
|
+
return false;
|
748
|
+
}
|
749
|
+
// Traverse everything else, except comment nodes
|
750
|
+
}
|
751
|
+
else
|
752
|
+
if (elem.nodeType !== 8) {
|
753
|
+
if (iterate(elem.childNodes, cb) === false) {
|
754
|
+
return false;
|
755
|
+
}
|
756
|
+
}
|
757
|
+
}
|
758
|
+
|
759
|
+
},
|
760
|
+
isText = function(node){
|
761
|
+
return node.nodeType === 3 || node.nodeType === 4
|
762
|
+
},
|
763
|
+
iteratorMaker = function(toChildren, toNext){
|
764
|
+
return function( node, mustMoveRight ) {
|
765
|
+
// first try down
|
766
|
+
if(node[toChildren] && !mustMoveRight){
|
767
|
+
return isText(node[toChildren]) ?
|
768
|
+
node[toChildren] :
|
769
|
+
arguments.callee(node[toChildren])
|
770
|
+
} else if(node[toNext]) {
|
771
|
+
return isText(node[toNext]) ?
|
772
|
+
node[toNext] :
|
773
|
+
arguments.callee(node[toNext])
|
774
|
+
} else if(node.parentNode){
|
775
|
+
return arguments.callee(node.parentNode, true)
|
776
|
+
}
|
777
|
+
}
|
778
|
+
},
|
779
|
+
getNextTextNode = iteratorMaker("firstChild","nextSibling"),
|
780
|
+
getPrevTextNode = iteratorMaker("lastChild","previousSibling"),
|
781
|
+
callMove = function(container, offset, howMany){
|
782
|
+
if(isText(container)){
|
783
|
+
return move(container, offset+howMany)
|
784
|
+
} else {
|
785
|
+
return container.childNodes[offset] ?
|
786
|
+
move(container.childNodes[offset] , howMany) :
|
787
|
+
move(container.lastChild, howMany , true)
|
788
|
+
return
|
789
|
+
}
|
790
|
+
},
|
791
|
+
move = function(from, howMany){
|
792
|
+
var mover = howMany < 0 ?
|
793
|
+
getPrevTextNode : getNextTextNode;
|
794
|
+
|
795
|
+
howMany = Math.abs(howMany);
|
796
|
+
|
797
|
+
if(!isText(from)){
|
798
|
+
from = mover(from)
|
799
|
+
}
|
800
|
+
while(from && howMany >= from.nodeValue.length){
|
801
|
+
hasMany = howMany- from.nodeValue.length;
|
802
|
+
from = mover(from)
|
803
|
+
}
|
804
|
+
return {
|
805
|
+
node: from,
|
806
|
+
offset: mover === getNextTextNode ?
|
807
|
+
howMany :
|
808
|
+
from.nodeValue.length - howMany
|
809
|
+
}
|
810
|
+
},
|
811
|
+
supportWhitespace,
|
812
|
+
isWhitespace = function(el){
|
813
|
+
if(supportWhitespace == null){
|
814
|
+
supportWhitespace = 'isElementContentWhitespace' in el;
|
815
|
+
}
|
816
|
+
return (supportWhitespace? el.isElementContentWhitespace :
|
817
|
+
(el.nodeType === 3 && '' == el.data.trim()));
|
818
|
+
|
819
|
+
},
|
820
|
+
// if a point is within a rectangle
|
821
|
+
within = function(rect, point){
|
822
|
+
|
823
|
+
return rect.left <= point.clientX && rect.left + rect.width >= point.clientX &&
|
824
|
+
rect.top <= point.clientY &&
|
825
|
+
rect.top + rect.height >= point.clientY
|
826
|
+
},
|
827
|
+
// if a rectangle is within another rectangle
|
828
|
+
withinRect = function(outer, inner){
|
829
|
+
return within(outer, {
|
830
|
+
clientX: inner.left,
|
831
|
+
clientY: inner.top
|
832
|
+
}) && //top left
|
833
|
+
within(outer, {
|
834
|
+
clientX: inner.left + inner.width,
|
835
|
+
clientY: inner.top
|
836
|
+
}) && //top right
|
837
|
+
within(outer, {
|
838
|
+
clientX: inner.left,
|
839
|
+
clientY: inner.top + inner.height
|
840
|
+
}) && //bottom left
|
841
|
+
within(outer, {
|
842
|
+
clientX: inner.left + inner.width,
|
843
|
+
clientY: inner.top + inner.height
|
844
|
+
}) //bottom right
|
845
|
+
},
|
846
|
+
// gets the scroll offset from a window
|
847
|
+
scrollOffset = function( win){
|
848
|
+
var win = win ||window;
|
849
|
+
doc = win.document.documentElement, body = win.document.body;
|
850
|
+
|
851
|
+
return {
|
852
|
+
left: (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0),
|
853
|
+
top: (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0)
|
854
|
+
};
|
855
|
+
};
|
856
|
+
|
857
|
+
|
858
|
+
support.moveToPoint = !!$.Range().range.moveToPoint
|
859
|
+
|
860
|
+
|
861
|
+
})(jQuery)
|