visualsearch-rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
})();
|