visualsearch-rails 0.0.1
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/.gitignore +7 -0
- data/.travis.yml +4 -0
- data/Changelog.md +4 -0
- data/Gemfile +2 -0
- data/Rakefile +5 -0
- data/Readme.md +24 -0
- data/app/assets/css/icons.css +19 -0
- data/app/assets/css/reset.css +30 -0
- data/app/assets/css/workspace.css +290 -0
- data/app/assets/images/cancel_search.png +0 -0
- data/app/assets/images/search_glyph.png +0 -0
- data/app/assets/javascripts/backbone-0.9.10.js +1498 -0
- data/app/assets/javascripts/dependencies.js +14843 -0
- data/app/assets/javascripts/jquery.ui.autocomplete.js +614 -0
- data/app/assets/javascripts/jquery.ui.core.js +324 -0
- data/app/assets/javascripts/jquery.ui.datepicker.js +5 -0
- data/app/assets/javascripts/jquery.ui.menu.js +621 -0
- data/app/assets/javascripts/jquery.ui.position.js +497 -0
- data/app/assets/javascripts/jquery.ui.widget.js +521 -0
- data/app/assets/javascripts/underscore-1.4.3.js +1221 -0
- data/app/assets/javascripts/visualsearch/js/models/search_facets.js +67 -0
- data/app/assets/javascripts/visualsearch/js/models/search_query.js +70 -0
- data/app/assets/javascripts/visualsearch/js/templates/search_box.jst +8 -0
- data/app/assets/javascripts/visualsearch/js/templates/search_facet.jst +9 -0
- data/app/assets/javascripts/visualsearch/js/templates/search_input.jst +1 -0
- data/app/assets/javascripts/visualsearch/js/templates/templates.js +7 -0
- data/app/assets/javascripts/visualsearch/js/utils/backbone_extensions.js +17 -0
- data/app/assets/javascripts/visualsearch/js/utils/hotkeys.js +99 -0
- data/app/assets/javascripts/visualsearch/js/utils/inflector.js +21 -0
- data/app/assets/javascripts/visualsearch/js/utils/jquery_extensions.js +197 -0
- data/app/assets/javascripts/visualsearch/js/utils/search_parser.js +87 -0
- data/app/assets/javascripts/visualsearch/js/views/search_box.js +447 -0
- data/app/assets/javascripts/visualsearch/js/views/search_facet.js +444 -0
- data/app/assets/javascripts/visualsearch/js/views/search_input.js +409 -0
- data/app/assets/javascripts/visualsearch/js/visualsearch.js +77 -0
- data/lib/generators/visual_search_install.rb +30 -0
- data/lib/visualsearch-rails.rb +2 -0
- data/lib/visualsearch/rails.rb +6 -0
- data/lib/visualsearch/version.rb +3 -0
- data/visualsearch-rails.gemspec +26 -0
- metadata +165 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
(function() {
|
2
|
+
|
3
|
+
var $ = jQuery; // Handle namespaced jQuery
|
4
|
+
|
5
|
+
// The model that holds individual search facets and their categories.
|
6
|
+
// Held in a collection by `VS.app.searchQuery`.
|
7
|
+
VS.model.SearchFacet = Backbone.Model.extend({
|
8
|
+
|
9
|
+
// Extract the category and value and serialize it in preparation for
|
10
|
+
// turning the entire searchBox into a search query that can be sent
|
11
|
+
// to the server for parsing and searching.
|
12
|
+
serialize : function() {
|
13
|
+
var category = this.quoteCategory(this.get('category'));
|
14
|
+
var value = VS.utils.inflector.trim(this.get('value'));
|
15
|
+
var remainder = this.get("app").options.remainder;
|
16
|
+
|
17
|
+
if (!value) return '';
|
18
|
+
|
19
|
+
if (!_.contains(this.get("app").options.unquotable || [], category) && category != remainder) {
|
20
|
+
value = this.quoteValue(value);
|
21
|
+
}
|
22
|
+
|
23
|
+
if (category != remainder) {
|
24
|
+
category = category + ': ';
|
25
|
+
} else {
|
26
|
+
category = "";
|
27
|
+
}
|
28
|
+
return category + value;
|
29
|
+
},
|
30
|
+
|
31
|
+
// Wrap categories that have spaces or any kind of quote with opposite matching
|
32
|
+
// quotes to preserve the complex category during serialization.
|
33
|
+
quoteCategory : function(category) {
|
34
|
+
var hasDoubleQuote = (/"/).test(category);
|
35
|
+
var hasSingleQuote = (/'/).test(category);
|
36
|
+
var hasSpace = (/\s/).test(category);
|
37
|
+
|
38
|
+
if (hasDoubleQuote && !hasSingleQuote) {
|
39
|
+
return "'" + category + "'";
|
40
|
+
} else if (hasSpace || (hasSingleQuote && !hasDoubleQuote)) {
|
41
|
+
return '"' + category + '"';
|
42
|
+
} else {
|
43
|
+
return category;
|
44
|
+
}
|
45
|
+
},
|
46
|
+
|
47
|
+
// Wrap values that have quotes in opposite matching quotes. If a value has
|
48
|
+
// both single and double quotes, just use the double quotes.
|
49
|
+
quoteValue : function(value) {
|
50
|
+
var hasDoubleQuote = (/"/).test(value);
|
51
|
+
var hasSingleQuote = (/'/).test(value);
|
52
|
+
|
53
|
+
if (hasDoubleQuote && !hasSingleQuote) {
|
54
|
+
return "'" + value + "'";
|
55
|
+
} else {
|
56
|
+
return '"' + value + '"';
|
57
|
+
}
|
58
|
+
},
|
59
|
+
|
60
|
+
// If provided, use a custom label instead of the raw value.
|
61
|
+
label : function() {
|
62
|
+
return this.get('label') || this.get('value');
|
63
|
+
}
|
64
|
+
|
65
|
+
});
|
66
|
+
|
67
|
+
})();
|
@@ -0,0 +1,70 @@
|
|
1
|
+
(function() {
|
2
|
+
|
3
|
+
var $ = jQuery; // Handle namespaced jQuery
|
4
|
+
|
5
|
+
// Collection which holds all of the individual facets (category: value).
|
6
|
+
// Used for finding and removing specific facets.
|
7
|
+
VS.model.SearchQuery = Backbone.Collection.extend({
|
8
|
+
|
9
|
+
// Model holds the category and value of the facet.
|
10
|
+
model : VS.model.SearchFacet,
|
11
|
+
|
12
|
+
// Turns all of the facets into a single serialized string.
|
13
|
+
serialize : function() {
|
14
|
+
return this.map(function(facet){ return facet.serialize(); }).join(' ');
|
15
|
+
},
|
16
|
+
|
17
|
+
facets : function() {
|
18
|
+
return this.map(function(facet) {
|
19
|
+
var value = {};
|
20
|
+
value[facet.get('category')] = facet.get('value');
|
21
|
+
return value;
|
22
|
+
});
|
23
|
+
},
|
24
|
+
|
25
|
+
// Find a facet by its category. Multiple facets with the same category
|
26
|
+
// is fine, but only the first is returned.
|
27
|
+
find : function(category) {
|
28
|
+
var facet = this.detect(function(facet) {
|
29
|
+
return facet.get('category').toLowerCase() == category.toLowerCase();
|
30
|
+
});
|
31
|
+
return facet && facet.get('value');
|
32
|
+
},
|
33
|
+
|
34
|
+
// Counts the number of times a specific category is in the search query.
|
35
|
+
count : function(category) {
|
36
|
+
return this.select(function(facet) {
|
37
|
+
return facet.get('category').toLowerCase() == category.toLowerCase();
|
38
|
+
}).length;
|
39
|
+
},
|
40
|
+
|
41
|
+
// Returns an array of extracted values from each facet in a category.
|
42
|
+
values : function(category) {
|
43
|
+
var facets = this.select(function(facet) {
|
44
|
+
return facet.get('category').toLowerCase() == category.toLowerCase();
|
45
|
+
});
|
46
|
+
return _.map(facets, function(facet) { return facet.get('value'); });
|
47
|
+
},
|
48
|
+
|
49
|
+
// Checks all facets for matches of either a category or both category and value.
|
50
|
+
has : function(category, value) {
|
51
|
+
return this.any(function(facet) {
|
52
|
+
var categoryMatched = facet.get('category').toLowerCase() == category.toLowerCase();
|
53
|
+
if (!value) return categoryMatched;
|
54
|
+
return categoryMatched && facet.get('value') == value;
|
55
|
+
});
|
56
|
+
},
|
57
|
+
|
58
|
+
// Used to temporarily hide specific categories and serialize the search query.
|
59
|
+
withoutCategory : function() {
|
60
|
+
var categories = _.map(_.toArray(arguments), function(cat) { return cat.toLowerCase(); });
|
61
|
+
return this.map(function(facet) {
|
62
|
+
if (!_.include(categories, facet.get('category').toLowerCase())) {
|
63
|
+
return facet.serialize();
|
64
|
+
};
|
65
|
+
}).join(' ');
|
66
|
+
}
|
67
|
+
|
68
|
+
});
|
69
|
+
|
70
|
+
})();
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<div class="VS-search">
|
2
|
+
<div class="VS-search-box-wrapper VS-search-box">
|
3
|
+
<div class="VS-icon VS-icon-search"></div>
|
4
|
+
<div class="VS-placeholder"></div>
|
5
|
+
<div class="VS-search-inner"></div>
|
6
|
+
<div class="VS-icon VS-icon-cancel VS-cancel-search-box" title="clear search"></div>
|
7
|
+
</div>
|
8
|
+
</div>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% if (model.has('category')) { %>
|
2
|
+
<div class="category"><%= model.get('category') %>:</div>
|
3
|
+
<% } %>
|
4
|
+
|
5
|
+
<div class="search_facet_input_container">
|
6
|
+
<input type="text" class="search_facet_input ui-menu VS-interface" value="" />
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div class="search_facet_remove VS-icon VS-icon-cancel"></div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<input type="text" class="ui-menu" />
|
@@ -0,0 +1,7 @@
|
|
1
|
+
(function(){
|
2
|
+
window.JST = window.JST || {};
|
3
|
+
|
4
|
+
window.JST['search_box'] = _.template('<div class="VS-search">\n <div class="VS-search-box-wrapper VS-search-box">\n <div class="VS-icon VS-icon-search"></div>\n <div class="VS-placeholder"></div>\n <div class="VS-search-inner"></div>\n <div class="VS-icon VS-icon-cancel VS-cancel-search-box" title="clear search"></div>\n </div>\n</div>');
|
5
|
+
window.JST['search_facet'] = _.template('<% if (model.has(\'category\')) { %>\n <div class="category"><%= model.get(\'category\') %>:</div>\n<% } %>\n\n<div class="search_facet_input_container">\n <input type="text" class="search_facet_input ui-menu VS-interface" value="" />\n</div>\n\n<div class="search_facet_remove VS-icon VS-icon-cancel"></div>');
|
6
|
+
window.JST['search_input'] = _.template('<input type="text" class="ui-menu" />');
|
7
|
+
})();
|
@@ -0,0 +1,17 @@
|
|
1
|
+
(function(){
|
2
|
+
|
3
|
+
var $ = jQuery; // Handle namespaced jQuery
|
4
|
+
|
5
|
+
// Makes the view enter a mode. Modes have both a 'mode' and a 'group',
|
6
|
+
// and are mutually exclusive with any other modes in the same group.
|
7
|
+
// Setting will update the view's modes hash, as well as set an HTML class
|
8
|
+
// of *[mode]_[group]* on the view's element. Convenient way to swap styles
|
9
|
+
// and behavior.
|
10
|
+
Backbone.View.prototype.setMode = function(mode, group) {
|
11
|
+
this.modes || (this.modes = {});
|
12
|
+
if (this.modes[group] === mode) return;
|
13
|
+
$(this.el).setMode(mode, group);
|
14
|
+
this.modes[group] = mode;
|
15
|
+
};
|
16
|
+
|
17
|
+
})();
|
@@ -0,0 +1,99 @@
|
|
1
|
+
(function() {
|
2
|
+
|
3
|
+
var $ = jQuery; // Handle namespaced jQuery
|
4
|
+
|
5
|
+
// DocumentCloud workspace hotkeys. To tell if a key is currently being pressed,
|
6
|
+
// just ask `VS.app.hotkeys.[key]` on `keypress`, or ask `VS.app.hotkeys.key(e)`
|
7
|
+
// on `keydown`.
|
8
|
+
//
|
9
|
+
// For the most headache-free way to use this utility, check modifier keys,
|
10
|
+
// like shift and command, with `VS.app.hotkeys.shift`, and check every other
|
11
|
+
// key with `VS.app.hotkeys.key(e) == 'key_name'`.
|
12
|
+
VS.app.hotkeys = {
|
13
|
+
|
14
|
+
// Keys that will be mapped to the `hotkeys` namespace.
|
15
|
+
KEYS: {
|
16
|
+
'16': 'shift',
|
17
|
+
'17': 'command',
|
18
|
+
'91': 'command',
|
19
|
+
'93': 'command',
|
20
|
+
'224': 'command',
|
21
|
+
'13': 'enter',
|
22
|
+
'37': 'left',
|
23
|
+
'38': 'upArrow',
|
24
|
+
'39': 'right',
|
25
|
+
'40': 'downArrow',
|
26
|
+
'46': 'delete',
|
27
|
+
'8': 'backspace',
|
28
|
+
'35': 'end',
|
29
|
+
'36': 'home',
|
30
|
+
'9': 'tab',
|
31
|
+
'188': 'comma'
|
32
|
+
},
|
33
|
+
|
34
|
+
// Binds global keydown and keyup events to listen for keys that match `this.KEYS`.
|
35
|
+
initialize : function() {
|
36
|
+
_.bindAll(this, 'down', 'up', 'blur');
|
37
|
+
$(document).bind('keydown', this.down);
|
38
|
+
$(document).bind('keyup', this.up);
|
39
|
+
$(window).bind('blur', this.blur);
|
40
|
+
},
|
41
|
+
|
42
|
+
// On `keydown`, turn on all keys that match.
|
43
|
+
down : function(e) {
|
44
|
+
var key = this.KEYS[e.which];
|
45
|
+
if (key) this[key] = true;
|
46
|
+
},
|
47
|
+
|
48
|
+
// On `keyup`, turn off all keys that match.
|
49
|
+
up : function(e) {
|
50
|
+
var key = this.KEYS[e.which];
|
51
|
+
if (key) this[key] = false;
|
52
|
+
},
|
53
|
+
|
54
|
+
// If an input is blurred, all keys need to be turned off, since they are no longer
|
55
|
+
// able to modify the document.
|
56
|
+
blur : function(e) {
|
57
|
+
for (var key in this.KEYS) this[this.KEYS[key]] = false;
|
58
|
+
},
|
59
|
+
|
60
|
+
// Check a key from an event and return the common english name.
|
61
|
+
key : function(e) {
|
62
|
+
return this.KEYS[e.which];
|
63
|
+
},
|
64
|
+
|
65
|
+
// Colon is special, since the value is different between browsers.
|
66
|
+
colon : function(e) {
|
67
|
+
var charCode = e.which;
|
68
|
+
return charCode && String.fromCharCode(charCode) == ":";
|
69
|
+
},
|
70
|
+
|
71
|
+
// Check a key from an event and match it against any known characters.
|
72
|
+
// The `keyCode` is different depending on the event type: `keydown` vs. `keypress`.
|
73
|
+
//
|
74
|
+
// These were determined by looping through every `keyCode` and `charCode` that
|
75
|
+
// resulted from `keydown` and `keypress` events and counting what was printable.
|
76
|
+
printable : function(e) {
|
77
|
+
var code = e.which;
|
78
|
+
if (e.type == 'keydown') {
|
79
|
+
if (code == 32 || // space
|
80
|
+
(code >= 48 && code <= 90) || // 0-1a-z
|
81
|
+
(code >= 96 && code <= 111) || // 0-9+-/*.
|
82
|
+
(code >= 186 && code <= 192) || // ;=,-./^
|
83
|
+
(code >= 219 && code <= 222)) { // (\)'
|
84
|
+
return true;
|
85
|
+
}
|
86
|
+
} else {
|
87
|
+
// [space]!"#$%&'()*+,-.0-9:;<=>?@A-Z[\]^_`a-z{|} and unicode characters
|
88
|
+
if ((code >= 32 && code <= 126) ||
|
89
|
+
(code >= 160 && code <= 500) ||
|
90
|
+
(String.fromCharCode(code) == ":")) {
|
91
|
+
return true;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
return false;
|
95
|
+
}
|
96
|
+
|
97
|
+
};
|
98
|
+
|
99
|
+
})();
|
@@ -0,0 +1,21 @@
|
|
1
|
+
(function() {
|
2
|
+
|
3
|
+
var $ = jQuery; // Handle namespaced jQuery
|
4
|
+
|
5
|
+
// Naive English transformations on words. Only used for a few transformations
|
6
|
+
// in VisualSearch.js.
|
7
|
+
VS.utils.inflector = {
|
8
|
+
|
9
|
+
// Delegate to the ECMA5 String.prototype.trim function, if available.
|
10
|
+
trim : function(s) {
|
11
|
+
return s.trim ? s.trim() : s.replace(/^\s+|\s+$/g, '');
|
12
|
+
},
|
13
|
+
|
14
|
+
// Escape strings that are going to be used in a regex. Escapes punctuation
|
15
|
+
// that would be incorrect in a regex.
|
16
|
+
escapeRegExp : function(s) {
|
17
|
+
return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
|
18
|
+
}
|
19
|
+
};
|
20
|
+
|
21
|
+
})();
|
@@ -0,0 +1,197 @@
|
|
1
|
+
(function() {
|
2
|
+
|
3
|
+
var $ = jQuery; // Handle namespaced jQuery
|
4
|
+
|
5
|
+
$.fn.extend({
|
6
|
+
|
7
|
+
// Makes the selector enter a mode. Modes have both a 'mode' and a 'group',
|
8
|
+
// and are mutually exclusive with any other modes in the same group.
|
9
|
+
// Setting will update the view's modes hash, as well as set an HTML class
|
10
|
+
// of *[mode]_[group]* on the view's element. Convenient way to swap styles
|
11
|
+
// and behavior.
|
12
|
+
setMode : function(state, group) {
|
13
|
+
group = group || 'mode';
|
14
|
+
var re = new RegExp("\\w+_" + group + "(\\s|$)", 'g');
|
15
|
+
var mode = (state === null) ? "" : state + "_" + group;
|
16
|
+
this.each(function() {
|
17
|
+
this.className = (this.className.replace(re, '')+' '+mode)
|
18
|
+
.replace(/\s\s/g, ' ');
|
19
|
+
});
|
20
|
+
return mode;
|
21
|
+
},
|
22
|
+
|
23
|
+
// When attached to an input element, this will cause the width of the input
|
24
|
+
// to match its contents. This calculates the width of the contents of the input
|
25
|
+
// by measuring a hidden shadow div that should match the styling of the input.
|
26
|
+
autoGrowInput: function() {
|
27
|
+
return this.each(function() {
|
28
|
+
var $input = $(this);
|
29
|
+
var $tester = $('<div />').css({
|
30
|
+
opacity : 0,
|
31
|
+
top : -9999,
|
32
|
+
left : -9999,
|
33
|
+
position : 'absolute',
|
34
|
+
whiteSpace : 'nowrap'
|
35
|
+
}).addClass('VS-input-width-tester').addClass('VS-interface');
|
36
|
+
|
37
|
+
// Watch for input value changes on all of these events. `resize`
|
38
|
+
// event is called explicitly when the input has been changed without
|
39
|
+
// a single keypress.
|
40
|
+
var events = 'keydown.autogrow keypress.autogrow ' +
|
41
|
+
'resize.autogrow change.autogrow';
|
42
|
+
$input.next('.VS-input-width-tester').remove();
|
43
|
+
$input.after($tester);
|
44
|
+
$input.unbind(events).bind(events, function(e, realEvent) {
|
45
|
+
if (realEvent) e = realEvent;
|
46
|
+
var value = $input.val();
|
47
|
+
|
48
|
+
// Watching for the backspace key is tricky because it may not
|
49
|
+
// actually be deleting the character, but instead the key gets
|
50
|
+
// redirected to move the cursor from facet to facet.
|
51
|
+
if (VS.app.hotkeys.key(e) == 'backspace') {
|
52
|
+
var position = $input.getCursorPosition();
|
53
|
+
if (position > 0) value = value.slice(0, position-1) +
|
54
|
+
value.slice(position, value.length);
|
55
|
+
} else if (VS.app.hotkeys.printable(e) &&
|
56
|
+
!VS.app.hotkeys.command) {
|
57
|
+
value += String.fromCharCode(e.which);
|
58
|
+
}
|
59
|
+
value = value.replace(/&/g, '&')
|
60
|
+
.replace(/\s/g,' ')
|
61
|
+
.replace(/</g, '<')
|
62
|
+
.replace(/>/g, '>');
|
63
|
+
|
64
|
+
$tester.html(value);
|
65
|
+
|
66
|
+
$input.width($tester.width() + 3 + parseInt($input.css('min-width')));
|
67
|
+
$input.trigger('updated.autogrow');
|
68
|
+
});
|
69
|
+
|
70
|
+
// Sets the width of the input on initialization.
|
71
|
+
$input.trigger('resize.autogrow');
|
72
|
+
});
|
73
|
+
},
|
74
|
+
|
75
|
+
|
76
|
+
// Cross-browser method used for calculating where the cursor is in an
|
77
|
+
// input field.
|
78
|
+
getCursorPosition: function() {
|
79
|
+
var position = 0;
|
80
|
+
var input = this.get(0);
|
81
|
+
|
82
|
+
if (document.selection) { // IE
|
83
|
+
input.focus();
|
84
|
+
var sel = document.selection.createRange();
|
85
|
+
var selLen = document.selection.createRange().text.length;
|
86
|
+
sel.moveStart('character', -input.value.length);
|
87
|
+
position = sel.text.length - selLen;
|
88
|
+
} else if (input && $(input).is(':visible') &&
|
89
|
+
input.selectionStart != null) { // Firefox/Safari
|
90
|
+
position = input.selectionStart;
|
91
|
+
}
|
92
|
+
|
93
|
+
return position;
|
94
|
+
},
|
95
|
+
|
96
|
+
// A simple proxy for `selectRange` that sets the cursor position in an
|
97
|
+
// input field.
|
98
|
+
setCursorPosition: function(position) {
|
99
|
+
return this.each(function() {
|
100
|
+
return $(this).selectRange(position, position);
|
101
|
+
});
|
102
|
+
},
|
103
|
+
|
104
|
+
// Cross-browser way to select text in an input field.
|
105
|
+
selectRange: function(start, end) {
|
106
|
+
return this.filter(':visible').each(function() {
|
107
|
+
if (this.setSelectionRange) { // FF/Webkit
|
108
|
+
this.focus();
|
109
|
+
this.setSelectionRange(start, end);
|
110
|
+
} else if (this.createTextRange) { // IE
|
111
|
+
var range = this.createTextRange();
|
112
|
+
range.collapse(true);
|
113
|
+
range.moveEnd('character', end);
|
114
|
+
range.moveStart('character', start);
|
115
|
+
if (end - start >= 0) range.select();
|
116
|
+
}
|
117
|
+
});
|
118
|
+
},
|
119
|
+
|
120
|
+
// Returns an object that contains the text selection range values for
|
121
|
+
// an input field.
|
122
|
+
getSelection: function() {
|
123
|
+
var input = this[0];
|
124
|
+
|
125
|
+
if (input.selectionStart != null) { // FF/Webkit
|
126
|
+
var start = input.selectionStart;
|
127
|
+
var end = input.selectionEnd;
|
128
|
+
return {
|
129
|
+
start : start,
|
130
|
+
end : end,
|
131
|
+
length : end-start,
|
132
|
+
text : input.value.substr(start, end-start)
|
133
|
+
};
|
134
|
+
} else if (document.selection) { // IE
|
135
|
+
var range = document.selection.createRange();
|
136
|
+
if (range) {
|
137
|
+
var textRange = input.createTextRange();
|
138
|
+
var copyRange = textRange.duplicate();
|
139
|
+
textRange.moveToBookmark(range.getBookmark());
|
140
|
+
copyRange.setEndPoint('EndToStart', textRange);
|
141
|
+
var start = copyRange.text.length;
|
142
|
+
var end = start + range.text.length;
|
143
|
+
return {
|
144
|
+
start : start,
|
145
|
+
end : end,
|
146
|
+
length : end-start,
|
147
|
+
text : range.text
|
148
|
+
};
|
149
|
+
}
|
150
|
+
}
|
151
|
+
return {start: 0, end: 0, length: 0};
|
152
|
+
}
|
153
|
+
|
154
|
+
});
|
155
|
+
|
156
|
+
// Debugging in Internet Explorer. This allows you to use
|
157
|
+
// `console.log(['message', var1, var2, ...])`. Just remove the `false` and
|
158
|
+
// add your console.logs. This will automatically stringify objects using
|
159
|
+
// `JSON.stringify', so you can read what's going out. Think of this as a
|
160
|
+
// *Diet Firebug Lite Zero with Lemon*.
|
161
|
+
if (false) {
|
162
|
+
window.console = {};
|
163
|
+
var _$ied;
|
164
|
+
window.console.log = function(msg) {
|
165
|
+
if (_.isArray(msg)) {
|
166
|
+
var message = msg[0];
|
167
|
+
var vars = _.map(msg.slice(1), function(arg) {
|
168
|
+
return JSON.stringify(arg);
|
169
|
+
}).join(' - ');
|
170
|
+
}
|
171
|
+
if(!_$ied){
|
172
|
+
_$ied = $('<div><ol></ol></div>').css({
|
173
|
+
'position': 'fixed',
|
174
|
+
'bottom': 10,
|
175
|
+
'left': 10,
|
176
|
+
'zIndex': 20000,
|
177
|
+
'width': $('body').width() - 80,
|
178
|
+
'border': '1px solid #000',
|
179
|
+
'padding': '10px',
|
180
|
+
'backgroundColor': '#fff',
|
181
|
+
'fontFamily': 'arial,helvetica,sans-serif',
|
182
|
+
'fontSize': '11px'
|
183
|
+
});
|
184
|
+
$('body').append(_$ied);
|
185
|
+
}
|
186
|
+
var $message = $('<li>'+message+' - '+vars+'</li>').css({
|
187
|
+
'borderBottom': '1px solid #999999'
|
188
|
+
});
|
189
|
+
_$ied.find('ol').append($message);
|
190
|
+
_.delay(function() {
|
191
|
+
$message.fadeOut(500);
|
192
|
+
}, 5000);
|
193
|
+
};
|
194
|
+
|
195
|
+
}
|
196
|
+
|
197
|
+
})();
|