picky-client 0.2.4 → 0.3.0
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/bin/picky-client +14 -0
- data/lib/picky-client/generator.rb +180 -0
- data/sinatra_prototype/Gemfile +13 -0
- data/sinatra_prototype/app.rb +59 -0
- data/sinatra_prototype/book.rb +42 -0
- data/sinatra_prototype/config.ru +2 -0
- data/sinatra_prototype/images/picky.png +0 -0
- data/sinatra_prototype/javascripts/compiler.jar +0 -0
- data/sinatra_prototype/javascripts/generate_bundles +25 -0
- data/sinatra_prototype/javascripts/jquery-1.3.2.js +4376 -0
- data/sinatra_prototype/javascripts/jquery-1.4.3.min.js +166 -0
- data/sinatra_prototype/javascripts/jquery.scrollTo-1.4.2.js +215 -0
- data/sinatra_prototype/javascripts/jquery.timer.js +75 -0
- data/sinatra_prototype/javascripts/picky.addination.js +36 -0
- data/sinatra_prototype/javascripts/picky.allocation_renderer.js +291 -0
- data/sinatra_prototype/javascripts/picky.allocations_cloud.js +91 -0
- data/sinatra_prototype/javascripts/picky.backend.js +86 -0
- data/sinatra_prototype/javascripts/picky.client.js +62 -0
- data/sinatra_prototype/javascripts/picky.controller.js +107 -0
- data/sinatra_prototype/javascripts/picky.data.js +78 -0
- data/sinatra_prototype/javascripts/picky.extensions.js +15 -0
- data/sinatra_prototype/javascripts/picky.min.js +17 -0
- data/sinatra_prototype/javascripts/picky.results_renderer.js +103 -0
- data/sinatra_prototype/javascripts/picky.source.js.tar +0 -0
- data/sinatra_prototype/javascripts/picky.translations.js +50 -0
- data/sinatra_prototype/javascripts/picky.view.js +214 -0
- data/sinatra_prototype/library.csv +540 -0
- data/sinatra_prototype/stylesheets/stylesheet.css +175 -0
- data/sinatra_prototype/stylesheets/stylesheet.sass +216 -0
- data/sinatra_prototype/views/search.haml +107 -0
- data/spec/picky-client/generator_spec.rb +141 -0
- metadata +38 -6
@@ -0,0 +1,36 @@
|
|
1
|
+
var PickyAddination = function(view, results) {
|
2
|
+
|
3
|
+
// Calculate the addination range.
|
4
|
+
//
|
5
|
+
var calculateRange = function(data, correction) {
|
6
|
+
var correction = correction || 0;
|
7
|
+
var numberOfResults = 20; // Make parametrizable.
|
8
|
+
var offset = data.offset + numberOfResults + correction;
|
9
|
+
var end = offset + numberOfResults;
|
10
|
+
var total = data.total;
|
11
|
+
if (total < end) { end = total; }
|
12
|
+
return { offset:offset, start:(offset+1), end:end };
|
13
|
+
};
|
14
|
+
|
15
|
+
// Remove the addination.
|
16
|
+
//
|
17
|
+
var remove = function() {
|
18
|
+
results.find('.addination').remove();
|
19
|
+
};
|
20
|
+
this.remove = remove;
|
21
|
+
|
22
|
+
// Renders the addination;
|
23
|
+
//
|
24
|
+
var render = function(data) {
|
25
|
+
var total = data.total;
|
26
|
+
var range = calculateRange(data);
|
27
|
+
if (range.offset < total) {
|
28
|
+
var result = $("<div class='addination current'>" + t('results.addination.more') + "<div class='tothetop'><a href='javascript:$.scrollTo(0,{ duration: 500});'>↑</a></div></div>");
|
29
|
+
result.bind('click', { offset: range.offset }, view.addinationClicked);
|
30
|
+
return result;
|
31
|
+
} else {
|
32
|
+
return '';
|
33
|
+
}
|
34
|
+
};
|
35
|
+
this.render = render;
|
36
|
+
};
|
@@ -0,0 +1,291 @@
|
|
1
|
+
function AllocationRenderer(allocationChosenCallback) {
|
2
|
+
var self = this;
|
3
|
+
|
4
|
+
var locale = PickyI18n.locale;
|
5
|
+
|
6
|
+
var qualifiers = Localization.qualifiers && Localization.qualifiers[locale] || {};
|
7
|
+
var explanations = Localization.explanations && Localization.explanations[locale] || {};
|
8
|
+
var location_delimiter = Localization.location_delimiters[locale];
|
9
|
+
var explanation_delimiter = Localization.explanation_delimiters[locale];
|
10
|
+
|
11
|
+
// Those are of interest to the public.
|
12
|
+
//
|
13
|
+
this.text = '';
|
14
|
+
this.query = '';
|
15
|
+
this.explanation = '';
|
16
|
+
|
17
|
+
// TODO parametrize.
|
18
|
+
//
|
19
|
+
var no_ellipses = ['street_number', 'zipcode'];
|
20
|
+
|
21
|
+
// Contracts the originals of the zipped.
|
22
|
+
//
|
23
|
+
function contract(zipped) {
|
24
|
+
var hash = {}; // remembers the values
|
25
|
+
var insert = {}; // remembers the insertion locations
|
26
|
+
var remove = []; // remembers the remove indexes
|
27
|
+
var i;
|
28
|
+
for (i = 0, l = zipped.length; i < l; i++) {
|
29
|
+
var key = zipped[i][0];
|
30
|
+
if (key in hash) {
|
31
|
+
hash[key] = hash[key] + ' ' + zipped[i][1];
|
32
|
+
remove.push(i);
|
33
|
+
} else {
|
34
|
+
hash[key] = zipped[i][1];
|
35
|
+
insert[i] = key;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
// Insert the ones from the hash.
|
39
|
+
for (i in insert) {
|
40
|
+
zipped[i][1] = hash[insert[i]];
|
41
|
+
}
|
42
|
+
// Remove the ones from zipped we don't like. From the end.
|
43
|
+
for (i = remove.length-1; i >= 0; i--) {
|
44
|
+
zipped.remove(remove[i]);
|
45
|
+
}
|
46
|
+
return zipped;
|
47
|
+
};
|
48
|
+
this.contract = contract;
|
49
|
+
|
50
|
+
// TODO Parametrize!
|
51
|
+
var specialWhoCases = {
|
52
|
+
// Use the actual methods, not strings.
|
53
|
+
"maiden_name" : { format:"(-%1$s)", method:'capitalize', ignoreSingle:true },
|
54
|
+
"name" : { format:"<strong>%1$s</strong>", method:'toUpperCase', ignoreSingle:true },
|
55
|
+
"first_name" : { format:"%1$s", method:"capitalize" }
|
56
|
+
};
|
57
|
+
function handleWho(both, singleParam) {
|
58
|
+
var single = singleParam || false;
|
59
|
+
var allocation = both[0];
|
60
|
+
var word = both[1];
|
61
|
+
|
62
|
+
var formatting = specialWhoCases[allocation];
|
63
|
+
if (formatting) {
|
64
|
+
if (formatting.method) { word = word[formatting.method](); }
|
65
|
+
if (formatting.format) { word = formatting.format.replace(/%1\$s/, word); }
|
66
|
+
}
|
67
|
+
var explanation = explanations[allocation] || allocation;
|
68
|
+
if (single && !(formatting && formatting.ignoreSingle)) { return word + ' (' + explanation + ')'; }
|
69
|
+
|
70
|
+
return word;
|
71
|
+
}
|
72
|
+
// Handles the first (who) part.
|
73
|
+
//
|
74
|
+
// Rules:
|
75
|
+
// * If there is no thing, do return an empty string.
|
76
|
+
// * If there is only one thing, add an explanation.
|
77
|
+
// * If there are multiple things, handle special cases.
|
78
|
+
// Without special cases, the name is always in front.
|
79
|
+
// The things are separated by commas, and explained.
|
80
|
+
// If there are multiple instances of the same category, they are contracted.
|
81
|
+
//
|
82
|
+
// Note: to not disconnect the explanation from the query text.
|
83
|
+
//
|
84
|
+
function who(zipped) {
|
85
|
+
if (zipped.length == 0) {
|
86
|
+
return '';
|
87
|
+
} else if (zipped.length == 1) {
|
88
|
+
return handleWho(zipped[0], true);
|
89
|
+
} else {
|
90
|
+
// else, sort, special cases etc.
|
91
|
+
var result = [];
|
92
|
+
var append = [];
|
93
|
+
zipped = contract(zipped);
|
94
|
+
for (var i = 0, l = zipped.length; i < l; i++) {
|
95
|
+
if (zipped[i][0] == 'first_name') {
|
96
|
+
result.unshift(handleWho(zipped[i])); // prepend the first name
|
97
|
+
} else {
|
98
|
+
if (zipped[i][0] == 'maiden_name') {
|
99
|
+
append.push(handleWho(zipped[i]));
|
100
|
+
} else {
|
101
|
+
result.push(handleWho(zipped[i]));
|
102
|
+
}
|
103
|
+
}
|
104
|
+
};
|
105
|
+
if (append.length > 0) { result.push(append); };
|
106
|
+
return result.join(' ');
|
107
|
+
}
|
108
|
+
}
|
109
|
+
this.who = who;
|
110
|
+
|
111
|
+
function replacerFor(zipped) {
|
112
|
+
return function(_, category) {
|
113
|
+
for (var i = 0, l = zipped.length; i < l; i++) {
|
114
|
+
if (zipped[i][0] == category) { return zipped[i][1]; };
|
115
|
+
};
|
116
|
+
return '';
|
117
|
+
};
|
118
|
+
};
|
119
|
+
|
120
|
+
// Handles the second (where) part.
|
121
|
+
//
|
122
|
+
// Rules:
|
123
|
+
// * If there is no location, do return an empty string.
|
124
|
+
// * If there is only a zipcode, add a [<city explanation>].
|
125
|
+
// * If there is only a city, add nothing.
|
126
|
+
// * If there are both, zipcode needs to be first.
|
127
|
+
// TODO Contraction of multiple "cities" and/or zipcode.
|
128
|
+
//
|
129
|
+
var locations = {
|
130
|
+
'zipcode':(':zipcode [' + explanations.city + ']'),
|
131
|
+
'city':':city',
|
132
|
+
'city,zipcode':':zipcode :city'
|
133
|
+
};
|
134
|
+
function where(zipped) {
|
135
|
+
if (zipped.length == 0) { return ''; };
|
136
|
+
zipped = contract(zipped);
|
137
|
+
var key_ary = zipped;
|
138
|
+
key_ary.sort(function(zipped1, zipped2) {
|
139
|
+
return zipped1[0] < zipped2[0] ? -1 : 1;
|
140
|
+
});
|
141
|
+
// Now that it's sorted, get the right string.
|
142
|
+
var key = [];
|
143
|
+
for (var i = 0, l = key_ary.length; i < l; i++) {
|
144
|
+
key.push(key_ary[i][0]);
|
145
|
+
};
|
146
|
+
var loc = locations[key];
|
147
|
+
// Replace inside string.
|
148
|
+
var result = loc.replace(/:(zipcode|city)/g, replacerFor(zipped));
|
149
|
+
return result;
|
150
|
+
};
|
151
|
+
this.where = where;
|
152
|
+
|
153
|
+
function handleSingleWhat(both) {
|
154
|
+
var allocation = both[0];
|
155
|
+
var word = both[1];
|
156
|
+
|
157
|
+
var explanation = explanations[allocation] || allocation;
|
158
|
+
|
159
|
+
return word + ' (' + explanation + ')';
|
160
|
+
}
|
161
|
+
function what(zipped) {
|
162
|
+
if (zipped.length == 0) { return ''; };
|
163
|
+
|
164
|
+
result = [];
|
165
|
+
zipped = contract(zipped);
|
166
|
+
for (var i = 0, l = zipped.length; i < l; i++) {
|
167
|
+
result.push(handleSingleWhat(zipped[i]));
|
168
|
+
}
|
169
|
+
|
170
|
+
return result.join(', ');
|
171
|
+
};
|
172
|
+
this.what = what;
|
173
|
+
|
174
|
+
// Orders the allocation identifiers according to
|
175
|
+
// [<who>, <what>, <where>]
|
176
|
+
// Returns a reordered array.
|
177
|
+
//
|
178
|
+
// TODO Rename "group".
|
179
|
+
//
|
180
|
+
var who_qualifiers = ['first_name', 'name', 'maiden_name'];
|
181
|
+
var where_qualifiers = ['zipcode', 'city'];
|
182
|
+
function trisect(zipped) {
|
183
|
+
var who_parts = [];
|
184
|
+
var what_parts = [];
|
185
|
+
var where_parts = [];
|
186
|
+
|
187
|
+
for (var i = 0, l = zipped.length; i < l; i++) {
|
188
|
+
var combination = zipped[i];
|
189
|
+
if (where_qualifiers.include(combination[0])) {
|
190
|
+
where_parts.push(combination);
|
191
|
+
} else if (who_qualifiers.include(combination[0])) {
|
192
|
+
who_parts.push(combination);
|
193
|
+
} else {
|
194
|
+
what_parts.push(combination);
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
// Ellipsisize the last part
|
199
|
+
var alloc_part;
|
200
|
+
if (where_parts.length > 0) {
|
201
|
+
alloc_part = where_parts[where_parts.length-1];
|
202
|
+
} else if (what_parts.length > 0) {
|
203
|
+
alloc_part = what_parts[what_parts.length-1];
|
204
|
+
} else if (who_parts.length > 0) {
|
205
|
+
alloc_part = who_parts[who_parts.length-1];
|
206
|
+
} // always results in a part
|
207
|
+
if (!no_ellipses.include(alloc_part[0])) { alloc_part[1] += '...'; } // TODO *
|
208
|
+
|
209
|
+
var rendered_who = who(who_parts);
|
210
|
+
var rendered_what = what(what_parts);
|
211
|
+
var rendered_where = where(where_parts);
|
212
|
+
return [rendered_who, rendered_what, rendered_where];
|
213
|
+
};
|
214
|
+
this.trisect = trisect;
|
215
|
+
|
216
|
+
// Fuses a possible who part to a possible what part to a possible where part.
|
217
|
+
//
|
218
|
+
// e.g. <who>, <what> in <where>
|
219
|
+
//
|
220
|
+
// Note: to not disconnect the location delimiter (e.g. "in") from the location.
|
221
|
+
//
|
222
|
+
// TODO Parametrize!
|
223
|
+
//
|
224
|
+
var who_what_join = ', ';
|
225
|
+
var whowhat_where_join = ' ' + location_delimiter + ' ';
|
226
|
+
function fuse(parts) {
|
227
|
+
var who = parts[0], what = parts[1], where = parts[2];
|
228
|
+
var who_what = '';
|
229
|
+
if (who != '') {
|
230
|
+
if (what != '') { who_what = [who, what].join(who_what_join); } else { who_what = who; }
|
231
|
+
} else {
|
232
|
+
who_what = what;
|
233
|
+
}
|
234
|
+
if (where == '') { return who_what; };
|
235
|
+
return [who_what, where].join(whowhat_where_join);
|
236
|
+
};
|
237
|
+
this.fuse = fuse;
|
238
|
+
|
239
|
+
// Creates a query string from combination and originals.
|
240
|
+
//
|
241
|
+
function querify(zipped) {
|
242
|
+
var query_parts = [];
|
243
|
+
var qualifier;
|
244
|
+
for (var i in zipped) {
|
245
|
+
qualifier = zipped[i][0];
|
246
|
+
qualifier = qualifiers[qualifier] || qualifier; // Use the returned qualifier if none is given.
|
247
|
+
query_parts[i] = qualifier + ':' + zipped[i][1];
|
248
|
+
};
|
249
|
+
return query_parts.join(' ');
|
250
|
+
};
|
251
|
+
this.querify = querify;
|
252
|
+
|
253
|
+
//
|
254
|
+
//
|
255
|
+
function suggestify(zipped) {
|
256
|
+
return fuse(trisect(zipped));
|
257
|
+
};
|
258
|
+
|
259
|
+
|
260
|
+
// Generates the text and the link.
|
261
|
+
//
|
262
|
+
var generate = function() {
|
263
|
+
this.query = querify(combination);
|
264
|
+
this.text = suggestify(combination);
|
265
|
+
|
266
|
+
return self;
|
267
|
+
};
|
268
|
+
|
269
|
+
//
|
270
|
+
//
|
271
|
+
var listItem = function(text, count) {
|
272
|
+
return $('<li><div class="text">' + text + '</div><div class="count">' + count + '</div></li>');
|
273
|
+
};
|
274
|
+
|
275
|
+
var render = function(allocation) {
|
276
|
+
var combination = allocation.combination;
|
277
|
+
var type = allocation.type;
|
278
|
+
var count = allocation.count;
|
279
|
+
|
280
|
+
var query = querify(combination);
|
281
|
+
|
282
|
+
var item = listItem(suggestify(combination), count);
|
283
|
+
|
284
|
+
// TODO Move this outwards?
|
285
|
+
//
|
286
|
+
item.bind('click', { query: query }, allocationChosenCallback);
|
287
|
+
return item;
|
288
|
+
};
|
289
|
+
this.render = render;
|
290
|
+
|
291
|
+
};
|
@@ -0,0 +1,91 @@
|
|
1
|
+
var PickyAllocationsCloud = function(view) {
|
2
|
+
|
3
|
+
var allocations = $('#picky .allocations');
|
4
|
+
var shownAllocations = allocations.find('.shown');
|
5
|
+
var showMoreAllocations = allocations.find('.more');
|
6
|
+
var hiddenAllocations = allocations.find('.hidden');
|
7
|
+
|
8
|
+
// Show the cloud.
|
9
|
+
//
|
10
|
+
var show = function(data) {
|
11
|
+
render(data.allocations);
|
12
|
+
allocations.show();
|
13
|
+
};
|
14
|
+
// Hide the cloud.
|
15
|
+
//
|
16
|
+
var hide = function() {
|
17
|
+
allocations.hide();
|
18
|
+
};
|
19
|
+
|
20
|
+
var clearAllocationCloud = function() {
|
21
|
+
shownAllocations.empty();
|
22
|
+
showMoreAllocations.hide();
|
23
|
+
hiddenAllocations.empty().hide();
|
24
|
+
};
|
25
|
+
|
26
|
+
//
|
27
|
+
//
|
28
|
+
var allocationChosen = function(event) {
|
29
|
+
hide();
|
30
|
+
view.allocationChosen(event);
|
31
|
+
};
|
32
|
+
|
33
|
+
var allocationRenderer = new AllocationRenderer(allocationChosen);
|
34
|
+
|
35
|
+
var createAllocationList = function(allocations) {
|
36
|
+
var shown = [];
|
37
|
+
allocations.each(function(i, allocation) {
|
38
|
+
shown.push(allocationRenderer.render(allocation));
|
39
|
+
|
40
|
+
// // TODO Combine.
|
41
|
+
// allocationRenderer.generate();
|
42
|
+
// var listItem = renderListItem(allocationRenderer);
|
43
|
+
|
44
|
+
// shown.push(listItem);
|
45
|
+
});
|
46
|
+
return shown;
|
47
|
+
};
|
48
|
+
|
49
|
+
var renderList = function(list) {
|
50
|
+
if (list.length == 0) {
|
51
|
+
return $('#search .allocations').hide();
|
52
|
+
}
|
53
|
+
var maxSuggestions = 3;
|
54
|
+
clearAllocationCloud();
|
55
|
+
|
56
|
+
if (list.length > maxSuggestions) {
|
57
|
+
$.each(list.slice(0,maxSuggestions-1), function(i, item) {
|
58
|
+
shownAllocations.append(item);
|
59
|
+
});
|
60
|
+
$.each(list.slice(maxSuggestions-1), function(i, item) {
|
61
|
+
hiddenAllocations.append(item);
|
62
|
+
});
|
63
|
+
showMoreAllocations.show();
|
64
|
+
}
|
65
|
+
else {
|
66
|
+
$.each(list, function(i, item) {
|
67
|
+
shownAllocations.append(item);
|
68
|
+
});
|
69
|
+
}
|
70
|
+
return $('#search .allocations').show();
|
71
|
+
};
|
72
|
+
|
73
|
+
// Render the allocation list.
|
74
|
+
//
|
75
|
+
var render = function(allocations) {
|
76
|
+
renderList(createAllocationList(allocations));
|
77
|
+
};
|
78
|
+
|
79
|
+
// Install handlers.
|
80
|
+
//
|
81
|
+
showMoreAllocations.click(function() {
|
82
|
+
showMoreAllocations.hide();
|
83
|
+
hiddenAllocations.show();
|
84
|
+
});
|
85
|
+
|
86
|
+
// Expose hide and show.
|
87
|
+
//
|
88
|
+
this.hide = hide;
|
89
|
+
this.show = show;
|
90
|
+
|
91
|
+
};
|
@@ -0,0 +1,86 @@
|
|
1
|
+
// Core search backend.
|
2
|
+
//
|
3
|
+
var PickyBackend = function(url) {
|
4
|
+
|
5
|
+
// Get returns the data without handling timestamps and whatnot.
|
6
|
+
//
|
7
|
+
var get = function(query, controllerCallback, offset, specificParams) {
|
8
|
+
var params = specificParams || {};
|
9
|
+
params = $.extend({ query: query, offset: offset }, specificParams);
|
10
|
+
|
11
|
+
// Wrap any data returned in a PickyData object.
|
12
|
+
//
|
13
|
+
var callback = function(data_hash) {
|
14
|
+
if (controllerCallback) { controllerCallback(new PickyData(data_hash)); }
|
15
|
+
};
|
16
|
+
|
17
|
+
$.ajax({ type: 'GET', url: url, data: params, success: callback, dataType: 'json'});
|
18
|
+
};
|
19
|
+
|
20
|
+
var search = function(query, controllerCallback, offset, specificParams, specificTimestamps) {
|
21
|
+
// Wrap the given callback.
|
22
|
+
//
|
23
|
+
var callback = function(data) {
|
24
|
+
if (controllerCallback) { controllerCallback(specificTimestamps, data); }
|
25
|
+
};
|
26
|
+
|
27
|
+
get(query, callback, offset, specificParams);
|
28
|
+
};
|
29
|
+
this.search = search;
|
30
|
+
};
|
31
|
+
|
32
|
+
// Live search backend.
|
33
|
+
//
|
34
|
+
var LiveBackend = function(url, callback) {
|
35
|
+
var backend = new PickyBackend(url);
|
36
|
+
|
37
|
+
var search = function(query, controllerCallback, offset, specificParams, fullTimestamps) {
|
38
|
+
var specificTimestamps = fullTimestamps || {};
|
39
|
+
|
40
|
+
latestRequestTimestamp = new Date();
|
41
|
+
specificTimestamps.live = latestRequestTimestamp;
|
42
|
+
|
43
|
+
// Wrap the given callback.
|
44
|
+
//
|
45
|
+
// Note: Binds the latest request timestamp for later comparison.
|
46
|
+
//
|
47
|
+
var callback = function(timestamps, data) {
|
48
|
+
if (!timestamps.live || timestamps.live == latestRequestTimestamp) {
|
49
|
+
if (controllerCallback) { controllerCallback(data); }
|
50
|
+
};
|
51
|
+
};
|
52
|
+
|
53
|
+
// Pass in the timestamp for later comparison.
|
54
|
+
//
|
55
|
+
backend.search(query, callback, offset, specificParams, specificTimestamps);
|
56
|
+
};
|
57
|
+
this.search = search;
|
58
|
+
};
|
59
|
+
|
60
|
+
// Full search backend.
|
61
|
+
//
|
62
|
+
var FullBackend = function(url) {
|
63
|
+
var backend = new PickyBackend(url);
|
64
|
+
|
65
|
+
var search = function(query, controllerCallback, offset, specificParams, givenTimestamps) {
|
66
|
+
var specificTimestamps = givenTimestamps || {};
|
67
|
+
|
68
|
+
latestRequestTimestamp = new Date();
|
69
|
+
specificTimestamps.full = latestRequestTimestamp;
|
70
|
+
|
71
|
+
// Wrap the given callback.
|
72
|
+
//
|
73
|
+
// Note: Binds the latest request timestamp for later comparison.
|
74
|
+
//
|
75
|
+
var callback = function(timestamps, data) {
|
76
|
+
if (!timestamps.full || timestamps.full == latestRequestTimestamp) {
|
77
|
+
if (controllerCallback) { controllerCallback(data); }
|
78
|
+
};
|
79
|
+
};
|
80
|
+
|
81
|
+
// Pass in the timestamp for later comparison.
|
82
|
+
//
|
83
|
+
backend.search(query, callback, offset, specificParams, specificTimestamps);
|
84
|
+
};
|
85
|
+
this.search = search;
|
86
|
+
};
|