rostra 0.0.15 → 0.0.16

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,8 +7,10 @@
7
7
  //= require jquery
8
8
  //= require jquery_ujs
9
9
  //= require rails.validations
10
+ //= require rostra/rails.validations.custom
10
11
  //= require rostra/jquery.jeditable
11
12
  //= require rostra/tiny_mce/jquery.tinymce
13
+ //= require rostra/jquery.autoSuggest
12
14
 
13
15
  $(document).ready(function() {
14
16
 
@@ -23,6 +25,22 @@ $(document).ready(function() {
23
25
  }();
24
26
 
25
27
 
28
+ $('input.tokenize').each(function() {
29
+ var input = $(this);
30
+ var ajax_url = input.attr('data-ajax_url');
31
+ var name = input.attr('name');
32
+ var prefill = input.val();
33
+
34
+ input.autoSuggest(ajax_url, {
35
+ startText: '',
36
+ emptyText:'Press comma or tab to add this tag',
37
+ preFill: prefill
38
+ });
39
+
40
+ $('input.as-values').attr('name', name); // force name to not
41
+ });
42
+
43
+
26
44
  var answer_comment_form = function() {
27
45
  $('a.leave_comment').click(function() {
28
46
  $(this).closest('.answer').find('form.comment').fadeIn();
@@ -0,0 +1,419 @@
1
+ /*
2
+ * AutoSuggest
3
+ * Copyright 2009-2010 Drew Wilson
4
+ * www.drewwilson.com
5
+ * code.drewwilson.com/entry/autosuggest-jquery-plugin
6
+ *
7
+ * Forked by Wu Yuntao
8
+ * github.com/wuyuntao/jquery-autosuggest
9
+ *
10
+ * Version 1.6.2
11
+ *
12
+ * This Plug-In will auto-complete or auto-suggest completed search queries
13
+ * for you as you type. You can add multiple selections and remove them on
14
+ * the fly. It supports keybord navigation (UP + DOWN + RETURN), as well
15
+ * as multiple AutoSuggest fields on the same page.
16
+ *
17
+ * Inspied by the Autocomplete plugin by: J歳n Zaefferer
18
+ * and the Facelist plugin by: Ian Tearle (iantearle.com)
19
+ *
20
+ * This AutoSuggest jQuery plug-in is dual licensed under the MIT and GPL licenses:
21
+ * http://www.opensource.org/licenses/mit-license.php
22
+ * http://www.gnu.org/licenses/gpl.html
23
+ */
24
+
25
+ (function($){
26
+ $.fn.autoSuggest = function(data, options) {
27
+ var defaults = {
28
+ asHtmlID: false,
29
+ startText: "Enter Name Here",
30
+ usePlaceholder: false,
31
+ emptyText: "No Results Found",
32
+ preFill: {},
33
+ limitText: "No More Selections Are Allowed",
34
+ selectedItemProp: "value", //name of object property
35
+ selectedValuesProp: "value", //name of object property
36
+ searchObjProps: "value", //comma separated list of object property names
37
+ queryParam: "q",
38
+ retrieveLimit: false, //number for 'limit' param on ajax request
39
+ extraParams: "",
40
+ matchCase: false,
41
+ minChars: 1,
42
+ keyDelay: 400,
43
+ resultsHighlight: true,
44
+ neverSubmit: false,
45
+ selectionLimit: false,
46
+ showResultList: true,
47
+ showResultListWhenNoMatch: false,
48
+ start: function(){},
49
+ selectionClick: function(elem){},
50
+ selectionAdded: function(elem){},
51
+ selectionRemoved: function(elem){ elem.remove(); },
52
+ formatList: false, //callback function
53
+ beforeRetrieve: function(string){ return string; },
54
+ retrieveComplete: function(data){ return data; },
55
+ resultClick: function(data){},
56
+ resultsComplete: function(){}
57
+ };
58
+ var opts = $.extend(defaults, options);
59
+
60
+ function countValidItems(data) { var n = 0; for (k in data) if (data.hasOwnProperty(k)) n++; return n; }
61
+
62
+ var d_fetcher;
63
+ if(typeof data == "function") {
64
+ d_fetcher = data;
65
+ } else if(typeof data == "string") {
66
+ d_fetcher = function(query, next) {
67
+ var limit = "";
68
+ if(opts.retrieveLimit){
69
+ limit = "&limit="+encodeURIComponent(opts.retrieveLimit);
70
+ }
71
+ $.getJSON(data+"?"+opts.queryParam+"="+encodeURIComponent(query)+limit+opts.extraParams, function(data){
72
+ var new_data = opts.retrieveComplete.call(this, data);
73
+ next(new_data, query);
74
+ });
75
+ };
76
+ } else if(typeof data == "object" && countValidItems(data) > 0) {
77
+ d_fetcher = function(query, next) { next(data, query); };
78
+ }
79
+
80
+ if(d_fetcher) {
81
+ return this.each(function(x){
82
+ if(!opts.asHtmlID){
83
+ x = x+""+Math.floor(Math.random()*100); //this ensures there will be unique IDs on the page if autoSuggest() is called multiple times
84
+ var x_id = "as-input-"+x;
85
+ } else {
86
+ x = opts.asHtmlID;
87
+ var x_id = x;
88
+ }
89
+ opts.start.call(this, {
90
+ add: function(data) {
91
+ add_selected_item(data, 'u' + $('li', selections_holder).length).addClass('blur');
92
+ },
93
+ remove: function(value) {
94
+ values_input.val(values_input.val().replace(","+value+",",","));
95
+ selections_holder.find('li[data-value = "' + value + '"]').remove();
96
+ },
97
+ });
98
+ var input = $(this);
99
+ input.attr("autocomplete","off").addClass("as-input").attr("id",x_id);
100
+ if (opts.usePlaceholder) {
101
+ input.attr('placeholder', opts.startText);
102
+ } else {
103
+ input.val(opts.startText);
104
+ }
105
+ var input_focus = false;
106
+
107
+ // Setup basic elements and render them to the DOM
108
+ input.wrap('<ul class="as-selections" id="as-selections-'+x+'"></ul>').wrap('<li class="as-original" id="as-original-'+x+'"></li>');
109
+ var selections_holder = $("#as-selections-"+x);
110
+ var org_li = $("#as-original-"+x);
111
+ var results_holder = $('<div class="as-results" id="as-results-'+x+'"></div>').hide();
112
+ var results_ul = $('<ul class="as-list"></ul>');
113
+ var values_input = $('<input type="hidden" class="as-values" name="as_values_'+x+'" id="as-values-'+x+'" />');
114
+ var prefill_value = "";
115
+ if(typeof opts.preFill == "string"){
116
+ var vals = opts.preFill.split(",");
117
+ for(var i=0; i < vals.length; i++){
118
+ var v_data = {};
119
+ v_data[opts.selectedValuesProp] = vals[i];
120
+ if(vals[i] != ""){
121
+ add_selected_item(v_data, "000"+i);
122
+ }
123
+ }
124
+ prefill_value = opts.preFill;
125
+ } else {
126
+ prefill_value = "";
127
+ var prefill_count = 0;
128
+ for (k in opts.preFill) if (opts.preFill.hasOwnProperty(k)) prefill_count++;
129
+ if(prefill_count > 0){
130
+ for(var i=0; i < prefill_count; i++){
131
+ var new_v = opts.preFill[i][opts.selectedValuesProp];
132
+ if(new_v == undefined){ new_v = ""; }
133
+ prefill_value = prefill_value+new_v+",";
134
+ if(new_v != ""){
135
+ add_selected_item(opts.preFill[i], "000"+i);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ if(prefill_value != ""){
141
+ input.val("");
142
+ var lastChar = prefill_value.substring(prefill_value.length-1);
143
+ if(lastChar != ","){ prefill_value = prefill_value+","; }
144
+ values_input.val(","+prefill_value);
145
+ $("li.as-selection-item", selections_holder).addClass("blur").removeClass("selected");
146
+ }
147
+ input.after(values_input);
148
+ selections_holder.click(function(){
149
+ input_focus = true;
150
+ input.focus();
151
+ }).mousedown(function(){ input_focus = false; }).after(results_holder);
152
+
153
+ var interval = null;
154
+ var timeout = null;
155
+ var prev = "";
156
+ var totalSelections = 0;
157
+ var tab_press = false;
158
+ var lastKeyPressCode = null;
159
+ var request = null;
160
+
161
+ // Handle input field events
162
+ input.focus(function(){
163
+ if(!opts.usePlaceholder && $(this).val() == opts.startText && values_input.val() == ""){
164
+ $(this).val("");
165
+ } else if(input_focus){
166
+ $("li.as-selection-item", selections_holder).removeClass("blur");
167
+ if($(this).val() != ""){
168
+ results_ul.css("width",selections_holder.outerWidth());
169
+ results_holder.show();
170
+ }
171
+ }
172
+ if (interval) clearInterval(interval);
173
+ interval = setInterval(function() {
174
+ if(opts.showResultList){
175
+ if(opts.selectionLimit && $("li.as-selection-item", selections_holder).length >= opts.selectionLimit){
176
+ results_ul.html('<li class="as-message">'+opts.limitText+'</li>');
177
+ results_holder.show();
178
+ } else {
179
+ keyChange();
180
+ }
181
+ }
182
+ }, opts.keyDelay);
183
+ input_focus = true;
184
+ if (opts.minChars == 0){
185
+ processRequest($(this).val());
186
+ }
187
+ return true;
188
+ }).blur(function(){
189
+ if (!opts.usePlaceholder && $(this).val() == "" && values_input.val() == "" && prefill_value == "" && opts.minChars > 0) {
190
+ $(this).val(opts.startText);
191
+ } else if(input_focus){
192
+ $("li.as-selection-item", selections_holder).addClass("blur").removeClass("selected");
193
+ results_holder.hide();
194
+ }
195
+ if (interval) clearInterval(interval);
196
+ }).keydown(function(e) {
197
+ // track last key pressed
198
+ lastKeyPressCode = e.keyCode;
199
+ first_focus = false;
200
+ switch(e.keyCode) {
201
+ case 38: // up
202
+ e.preventDefault();
203
+ moveSelection("up");
204
+ break;
205
+ case 40: // down
206
+ e.preventDefault();
207
+ moveSelection("down");
208
+ break;
209
+ case 8: // delete
210
+ if(input.val() == ""){
211
+ var last = values_input.val().split(",");
212
+ last = last[last.length - 2];
213
+ selections_holder.children().not(org_li.prev()).removeClass("selected");
214
+ if(org_li.prev().hasClass("selected")){
215
+ values_input.val(values_input.val().replace(","+last+",",","));
216
+ opts.selectionRemoved.call(this, org_li.prev());
217
+ } else {
218
+ opts.selectionClick.call(this, org_li.prev());
219
+ org_li.prev().addClass("selected");
220
+ }
221
+ }
222
+ if(input.val().length == 1){
223
+ results_holder.hide();
224
+ prev = "";
225
+ abortRequest();
226
+ }
227
+ if($(":visible",results_holder).length > 0){
228
+ if (timeout){ clearTimeout(timeout); }
229
+ timeout = setTimeout(function(){ keyChange(); }, opts.keyDelay);
230
+ }
231
+ break;
232
+ case 9: case 188: // tab or comma
233
+ tab_press = true;
234
+ var i_input = input.val().replace(/(,)/g, "");
235
+ var active = $("li.active:first", results_holder);
236
+ // Generate a new bubble with text when no suggestion selected
237
+ if(i_input !== "" && values_input.val().search(","+i_input+",") < 0 && i_input.length >= opts.minChars && active.length === 0){
238
+ e.preventDefault();
239
+ var n_data = {};
240
+ n_data[opts.selectedItemProp] = i_input;
241
+ n_data[opts.selectedValuesProp] = i_input;
242
+ var lis = $("li", selections_holder).length;
243
+ add_selected_item(n_data, "00"+(lis+1));
244
+ input.val("");
245
+ // Cancel previous request when new tag is added
246
+ abortRequest();
247
+ break;
248
+ }
249
+ case 13: // return
250
+ tab_press = false;
251
+ var active = $("li.active:first", results_holder);
252
+ if(active.length > 0){
253
+ active.click();
254
+ results_holder.hide();
255
+ }
256
+ if(opts.neverSubmit || active.length > 0){
257
+ e.preventDefault();
258
+ }
259
+ break;
260
+ // ignore if the following keys are pressed: [escape] [shift] [capslock]
261
+ case 27: // escape
262
+ case 16: // shift
263
+ case 20: // capslock
264
+ abortRequest();
265
+ results_holder.hide();
266
+ break;
267
+ }
268
+ });
269
+
270
+ function keyChange() {
271
+ // Since most IME does not trigger any key events, if we press [del]
272
+ // and type some chinese character, `lastKeyPressCode` will still be [del].
273
+ // This might cause problem so we move the line to key events section;
274
+ // ignore if the following keys are pressed: [del] [shift] [capslock]
275
+ // if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ){ return results_holder.hide(); }
276
+ var string = input.val().replace(/[\\]+|[\/]+/g,"");
277
+ if (string == prev) return;
278
+ prev = string;
279
+ if (string.length >= opts.minChars) {
280
+ selections_holder.addClass("loading");
281
+ processRequest(string);
282
+ } else {
283
+ selections_holder.removeClass("loading");
284
+ results_holder.hide();
285
+ }
286
+ }
287
+ function processRequest(string){
288
+ if(opts.beforeRetrieve){
289
+ string = opts.beforeRetrieve.call(this, string);
290
+ }
291
+ abortRequest();
292
+ d_fetcher(string, processData);
293
+ }
294
+ var num_count = 0;
295
+ function processData(data, query){
296
+ if (!opts.matchCase){ query = query.toLowerCase(); }
297
+ var matchCount = 0;
298
+ results_holder.html(results_ul.html("")).hide();
299
+ var d_count = countValidItems(data);
300
+ for(var i=0;i<d_count;i++){
301
+ var num = i;
302
+ num_count++;
303
+ var forward = false;
304
+ if(opts.searchObjProps == "value") {
305
+ var str = data[num].value;
306
+ } else {
307
+ var str = "";
308
+ var names = opts.searchObjProps.split(",");
309
+ for(var y=0;y<names.length;y++){
310
+ var name = $.trim(names[y]);
311
+ str = str+data[num][name]+" ";
312
+ }
313
+ }
314
+ if(str){
315
+ if (!opts.matchCase){ str = str.toLowerCase(); }
316
+ if(str.search(query) != -1 && values_input.val().search(","+data[num][opts.selectedValuesProp]+",") == -1){
317
+ forward = true;
318
+ }
319
+ }
320
+ if(forward){
321
+ var formatted = $('<li class="as-result-item" id="as-result-item-'+num+'"></li>').click(function(){
322
+ var raw_data = $(this).data("data");
323
+ var number = raw_data.num;
324
+ if($("#as-selection-"+number, selections_holder).length <= 0 && !tab_press){
325
+ var data = raw_data.attributes;
326
+ input.val("").focus();
327
+ prev = "";
328
+ add_selected_item(data, number);
329
+ opts.resultClick.call(this, raw_data);
330
+ results_holder.hide();
331
+ }
332
+ tab_press = false;
333
+ }).mousedown(function(){ input_focus = false; }).mouseover(function(){
334
+ $("li", results_ul).removeClass("active");
335
+ $(this).addClass("active");
336
+ }).data("data",{attributes: data[num], num: num_count});
337
+ var this_data = $.extend({},data[num]);
338
+ if (!opts.matchCase){
339
+ var regx = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "gi");
340
+ } else {
341
+ var regx = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + query + ")(?![^<>]*>)(?![^&;]+;)", "g");
342
+ }
343
+
344
+ if(opts.resultsHighlight && query.length > 0){
345
+ this_data[opts.selectedItemProp] = this_data[opts.selectedItemProp].replace(regx,"<em>$1</em>");
346
+ }
347
+ if(!opts.formatList){
348
+ formatted = formatted.html(this_data[opts.selectedItemProp]);
349
+ } else {
350
+ formatted = opts.formatList.call(this, this_data, formatted);
351
+ }
352
+ results_ul.append(formatted);
353
+ delete this_data;
354
+ matchCount++;
355
+ if(opts.retrieveLimit && opts.retrieveLimit == matchCount ){ break; }
356
+ }
357
+ }
358
+ selections_holder.removeClass("loading");
359
+ if(matchCount <= 0){
360
+ results_ul.html('<li class="as-message">'+opts.emptyText+'</li>');
361
+ }
362
+ results_ul.css("width", selections_holder.outerWidth());
363
+ if (matchCount > 0 || !opts.showResultListWhenNoMatch) {
364
+ results_holder.show();
365
+ }
366
+ opts.resultsComplete.call(this);
367
+ }
368
+
369
+ function add_selected_item(data, num){
370
+ values_input.val((values_input.val()||",")+data[opts.selectedValuesProp]+",");
371
+ var item = $('<li class="as-selection-item" id="as-selection-'+num+'" data-value="' + data[opts.selectedValuesProp] + '"></li>').click(function(){
372
+ opts.selectionClick.call(this, $(this));
373
+ selections_holder.children().removeClass("selected");
374
+ $(this).addClass("selected");
375
+ }).mousedown(function(){ input_focus = false; });
376
+ var close = $('<a class="as-close">&times;</a>').click(function(){
377
+ values_input.val(values_input.val().replace(","+data[opts.selectedValuesProp]+",",","));
378
+ opts.selectionRemoved.call(this, item);
379
+ input_focus = true;
380
+ input.focus();
381
+ return false;
382
+ });
383
+ org_li.before(item.html(data[opts.selectedItemProp]).prepend(close));
384
+ opts.selectionAdded.call(this, org_li.prev(), data[opts.selectedValuesProp]);
385
+ return org_li.prev();
386
+ }
387
+
388
+ function moveSelection(direction){
389
+ if($(":visible",results_holder).length > 0){
390
+ var lis = $("li", results_holder);
391
+ if(direction == "down"){
392
+ var start = lis.eq(0);
393
+ } else {
394
+ var start = lis.filter(":last");
395
+ }
396
+ var active = $("li.active:first", results_holder);
397
+ if(active.length > 0){
398
+ if(direction == "down"){
399
+ start = active.next();
400
+ } else {
401
+ start = active.prev();
402
+ }
403
+ }
404
+ lis.removeClass("active");
405
+ start.addClass("active");
406
+ }
407
+ }
408
+
409
+ function abortRequest() {
410
+ if (request) {
411
+ request.abort();
412
+ request = null;
413
+ }
414
+ }
415
+
416
+ });
417
+ }
418
+ };
419
+ })(jQuery);
@@ -0,0 +1,37 @@
1
+ var custom_validation_callbacks = {
2
+ // Taglist inputs are built using the autoSuggest jQuery plugin, which dynamically generates markup to create a nice
3
+ // facebook-like tag field. The plugin uses two inputs:
4
+ //
5
+ // 1) An input where the user types tag names. After clicking tab/comma, the plugin adds a new tag pill (`li`) to the
6
+ // list of tags (`ul`) and clears the input so the user can continue adding tags.
7
+ // 2) A hidden input which contains the value all the tags as a comma separated list. Tags are automatically appended
8
+ // whenever a the user enters a new tag.
9
+ //
10
+ // Rails uses the second input when writing to the database. This happens automatically since both inputs have the same
11
+ // `name` attribute, meaning only the second, hidden input gets passed along with the form parameters.
12
+ //
13
+ // Because of this markup, validation can be tricky since we really only care about the hidden input. Unfortunately,
14
+ // the `client_side_validation` gem adds the validaiton behavior to the first input. This callback essentially overrides
15
+ // the default behavior to ensure validations are ran against the second/hidden input.
16
+ //
17
+ taglist: function(element, event_data) {
18
+ var tag_list_input = element.hasClass('as-input');
19
+ if (tag_list_input) {
20
+ var taglist_valid = $.trim( element.next().val() ).match(/[^,]/); // has at least one tag and it's not just whitespace
21
+ var field = element.closest('.field');
22
+ field.append('<span class="error"></span>'); // Cuase sometimes it doesn't have a error span. Why?
23
+
24
+ if (taglist_valid) {
25
+ element.data('valid', true);
26
+ field.find('.error').text('');
27
+ } else {
28
+ element.data('valid', false);
29
+ field.find('.error').text("can't be blank");
30
+ }
31
+ }
32
+ }
33
+ };
34
+
35
+ clientSideValidations.callbacks.element.after = function(element, event_data) {
36
+ custom_validation_callbacks.taglist(element, event_data);
37
+ }
@@ -15,9 +15,6 @@
15
15
  .text {
16
16
  line-height: 18px;
17
17
  }
18
- nav {
19
- float: right;
20
- }
21
18
  #page_content {
22
19
  width: 630px;
23
20
  border-right: 1px solid #ccc;
@@ -40,8 +37,6 @@ body#questions.show #page_content {
40
37
  list-style: none;
41
38
  padding: 0;
42
39
  }
43
-
44
-
45
40
  img.avatar {
46
41
  float: right;
47
42
  box-shadow: 0 1px 4px #666;
@@ -0,0 +1,218 @@
1
+ /* AutoSuggest CSS - Version 1.2 */
2
+
3
+ ul.as-selections {
4
+ list-style-type: none;
5
+ border-bottom: 1px solid #b6b6b6;
6
+ border-left: 1px solid #aaa;
7
+ border-right: 1px solid #aaa;
8
+ padding: 5px 5px 1px;
9
+ margin: 0;
10
+ overflow: auto;
11
+ background-color: #fff;
12
+ box-shadow:inset 0 1px 2px #888;
13
+ -webkit-box-shadow:inset 0 1px 2px #888;
14
+ -moz-box-shadow:inset 0 1px 2px #888;
15
+ }
16
+
17
+ ul.as-selections li {
18
+ float: left;
19
+ margin: 1px 4px 1px 0;
20
+ }
21
+
22
+ ul.as-selections li.as-selection-item {
23
+ color: #2b3840;
24
+ font-size: 13px;
25
+ font-family: "Lucida Grande", arial, sans-serif;
26
+ text-shadow: 0 1px 1px #fff;
27
+ background-color: #ddeefe;
28
+ background-image: -moz-linear-gradient(top, #ddeefe, #bfe0f1);
29
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ddeefe), to(#bfe0f1));
30
+ border: 1px solid #acc3ec;
31
+ border-top-color: #c0d9e9;
32
+ padding: 2px 7px 2px 10px;
33
+ border-radius: 12px;
34
+ -webkit-border-radius: 12px;
35
+ -moz-border-radius: 12px;
36
+ box-shadow: 0 1px 1px #e4edf2;
37
+ -webkit-box-shadow: 0 1px 1px #e4edf2;
38
+ -moz-box-shadow: 0 1px 1px #e4edf2;
39
+ }
40
+
41
+ ul.as-selections li.as-selection-item:last-child {
42
+ margin-left: 30px;
43
+ }
44
+
45
+ ul.as-selections li.as-selection-item a.as-close {
46
+ float: right;
47
+ margin: 1px 0 0 7px;
48
+ padding: 0 2px;
49
+ cursor: pointer;
50
+ color: #5491be;
51
+ font-family: "Helvetica", helvetica, arial, sans-serif;
52
+ font-size: 14px;
53
+ font-weight: bold;
54
+ text-shadow: 0 1px 1px #fff;
55
+ -webkit-transition: color .1s ease-in;
56
+ }
57
+
58
+ ul.as-selections li.as-selection-item.blur {
59
+ color: #666666;
60
+ background-color: #f4f4f4;
61
+ background-image: -moz-linear-gradient(top, #f4f4f4, #d5d5d5);
62
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f4f4f4), to(#d5d5d5));
63
+ border-color: #bbb;
64
+ border-top-color: #ccc;
65
+ box-shadow: 0 1px 1px #e9e9e9;
66
+ -webkit-box-shadow: 0 1px 1px #e9e9e9;
67
+ -moz-box-shadow: 0 1px 1px #e9e9e9;
68
+ }
69
+
70
+ ul.as-selections li.as-selection-item.blur a.as-close {
71
+ color: #999;
72
+ }
73
+
74
+ ul.as-selections li:hover.as-selection-item {
75
+ color: #2b3840;
76
+ background-color: #bbd4f1;
77
+ background-image: -moz-linear-gradient(top, #bbd4f1, #a3c2e5);
78
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#bbd4f1), to(#a3c2e5));
79
+ border-color: #6da0e0;
80
+ border-top-color: #8bb7ed;
81
+ }
82
+
83
+ ul.as-selections li:hover.as-selection-item a.as-close {
84
+ color: #4d70b0;
85
+ }
86
+
87
+ ul.as-selections li.as-selection-item.selected {
88
+ border-color: #1f30e4;
89
+ }
90
+
91
+ ul.as-selections li.as-selection-item a:hover.as-close {
92
+ color: #1b3c65;
93
+ background: transparent;
94
+ }
95
+
96
+ ul.as-selections li.as-selection-item a:active.as-close {
97
+ color: #4d70b0;
98
+ }
99
+
100
+ ul.as-selections li.as-original {
101
+ margin-left: 0;
102
+ }
103
+
104
+ ul.as-selections li.as-original input {
105
+ border: none;
106
+ outline: none;
107
+ font-size: 13px;
108
+ width: 120px;
109
+ height: 18px;
110
+ padding-top: 3px;
111
+ background: white;
112
+ }
113
+
114
+ ul.as-list {
115
+ position: absolute;
116
+ list-style-type: none;
117
+ margin: 2px 0 0 0;
118
+ padding: 0;
119
+ font-size: 14px;
120
+ color: #000;
121
+ font-family: "Lucida Grande", arial, sans-serif;
122
+ background-color: #fff;
123
+ background-color: rgba(255,255,255,0.95);
124
+ z-index: 2;
125
+ box-shadow: 0 2px 12px #222;
126
+ -webkit-box-shadow: 0 2px 12px #222;
127
+ -moz-box-shadow: 0 2px 12px #222;
128
+ border-radius: 5px;
129
+ -webkit-border-radius: 5px;
130
+ -moz-border-radius: 5px;
131
+ }
132
+
133
+ li.as-result-item, li.as-message {
134
+ margin: 0 0 0 0;
135
+ padding: 5px 12px;
136
+ background-color: transparent;
137
+ border: 1px solid #fff;
138
+ border-bottom: 1px solid #ddd;
139
+ cursor: pointer;
140
+ border-radius: 5px;
141
+ -webkit-border-radius: 5px;
142
+ -moz-border-radius: 5px;
143
+ }
144
+
145
+ li:first-child.as-result-item {
146
+ margin: 0;
147
+ }
148
+
149
+ li.as-message {
150
+ margin: 0;
151
+ cursor: default;
152
+ }
153
+
154
+ li.as-result-item.active {
155
+ background-color: #3668d9;
156
+ background-image: -moz-linear-gradient(top, rgb(110, 129, 245), rgb(62, 82, 242));
157
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 64%, from(rgb(110, 129, 245)), to(rgb(62, 82, 242)));
158
+ border-color: #3342e8;
159
+ color: #fff;
160
+ text-shadow: 0 1px 2px #122042;
161
+ }
162
+
163
+ li.as-result-item em {
164
+ font-style: normal;
165
+ background: #444;
166
+ padding: 0 2px;
167
+ color: #fff;
168
+ }
169
+
170
+ li.as-result-item.active em {
171
+ background: #253f7a;
172
+ color: #fff;
173
+ }
174
+
175
+ /* Webkit Hacks */
176
+ @media screen and (-webkit-min-device-pixel-ratio:0) {
177
+ ul.as-selections {
178
+ border-top-width: 2px;
179
+ }
180
+ ul.as-selections li.as-selection-item {
181
+ padding-top: 0;
182
+ padding-bottom: 2px;
183
+ }
184
+ ul.as-selections li.as-selection-item a.as-close {
185
+ margin-top: -1px;
186
+ }
187
+ ul.as-selections li.as-original input {
188
+ height: 19px;
189
+ }
190
+ }
191
+
192
+ /* Opera Hacks */
193
+ @media all and (-webkit-min-device-pixel-ratio:10000), not all and (-webkit-min-device-pixel-ratio:0) {
194
+ ul.as-list {
195
+ border: 1px solid #888;
196
+ }
197
+ ul.as-selections li.as-selection-item a.as-close {
198
+ margin-left: 4px;
199
+ margin-top: 0;
200
+ }
201
+ }
202
+
203
+ /* IE Hacks */
204
+ ul.as-list {
205
+ border: 1px solid #888\9;
206
+ }
207
+ ul.as-selections li.as-selection-item a.as-close {
208
+ margin-left: 4px\9;
209
+ margin-top: 0\9;
210
+ }
211
+
212
+ /* Firefox 3.0 Hacks */
213
+ ul.as-list, x:-moz-any-link, x:default {
214
+ border: 1px solid #888;
215
+ }
216
+ BODY:first-of-type ul.as-list, x:-moz-any-link, x:default { /* Target FF 3.5+ */
217
+ border: none;
218
+ }
@@ -16,7 +16,7 @@ module Rostra
16
16
  if rostra_user.following?(@question)
17
17
  rostra_user.followed_questions.delete(@question)
18
18
  else
19
- rostra_user.followed_questions << @question
19
+ rostra_user.question_followings.create(question: @question, send_email_notifications: false)
20
20
  end
21
21
 
22
22
  respond_to do |format|
@@ -25,6 +25,14 @@ module Rostra
25
25
  end
26
26
  end
27
27
 
28
+ def tags
29
+ tags = Question.tag_counts.where("name like ?", "%#{params[:q]}%").limit(10)
30
+ respond_to do |format|
31
+ format.js { render :js => tags.map { |tag| {value: tag.name} }.to_json }
32
+ end
33
+ end
34
+
35
+
28
36
  def index
29
37
  if params[:tag_search].present?
30
38
  @questions = Question.tagged_with(params[:tag_search]).order('created_at desc').page(params[:page])
@@ -4,6 +4,8 @@ module Rostra
4
4
  has_many :answers
5
5
  has_many :question_followings
6
6
  has_many :followers, through: :question_followings, source: :user
7
+ has_many :notified_followers, through: :question_followings, source: :user,
8
+ conditions: { 'rostra_question_followings.send_email_notifications' => true }
7
9
 
8
10
  acts_as_taggable
9
11
  acts_as_voteable
@@ -18,13 +20,16 @@ module Rostra
18
20
 
19
21
  # Set number of questions per page for will paginate
20
22
  #
21
- per_page = Rostra::Config.number_of_question_per_page
23
+ paginates_per Rostra::Config.number_of_question_per_page
22
24
 
23
25
  # Finds questions asked within the last 15 days ordered by non-unique page views.
24
26
  #
25
27
  def self.trending(limit = 5)
26
28
  Question.where(created_at: (15.days.ago)..(Time.now)).limit(limit)
27
29
 
30
+ # Maybe try to use a subquery?
31
+ # User.where(:id => accounts.project(:user_id).where(accounts[:user_id].not_eq(6)))
32
+
28
33
  # This code doesn't work in postgres.
29
34
  # Question
30
35
  # .where(created_at: (15.days.ago)..(Time.now))
@@ -2,7 +2,7 @@
2
2
  <%= f.hidden_field :user_id, value: rostra_user.id %>
3
3
  <%= f.input :title %>
4
4
  <%= f.input :details, input_html: {class: 'wysiwyg'} %>
5
- <%= f.input :tag_list, label: 'Tags', hint: 'Separate tags with commas' %>
5
+ <%= f.input :tag_list, label: 'Tags', hint: 'Separate tags with commas', input_html: { class: 'tokenize', 'data-ajax_url' => tags_questions_path } %>
6
6
  <%= f.input :follow_by_email, as: :boolean, input_html: { checked: true }, label: "I want to follow answers and comments on this question" %>
7
7
  <%= f.submit @question.new_record? ? 'Post your question' : 'Update' %>
8
8
  <% end %>
@@ -44,7 +44,7 @@
44
44
  </div>
45
45
  <% end %>
46
46
 
47
- <%= will_paginate @posts %>
47
+ <%= paginate @questions %>
48
48
 
49
49
  <% end %>
50
50
  </div><!-- questions -->
data/config/routes.rb CHANGED
@@ -2,9 +2,12 @@ Rostra::Engine.routes.draw do
2
2
  resources :questions, except: [:destroy] do
3
3
  put :vote, on: :member
4
4
  put :toggle_following, on: :member
5
+ get :tags, on: :collection
6
+
5
7
  resources :answers, only: [:create, :edit, :update] do
6
8
  put :vote, on: :member
7
9
  end
10
+
8
11
  resources :comments, only: [:create, :update, :destroy]
9
12
  end
10
13
 
@@ -0,0 +1,5 @@
1
+ class AddSendEmailNotificationsToQuestionFollowings < ActiveRecord::Migration
2
+ def change
3
+ add_column :rostra_question_followings, :send_email_notifications, :boolean, default: true
4
+ end
5
+ end
@@ -11,7 +11,7 @@ module Rostra
11
11
  # don't send notification to the person that created the answer/comment.
12
12
  #
13
13
  def notify_followers
14
- question.followers.each do |user|
14
+ question.notified_followers.each do |user|
15
15
  next if self.user == user
16
16
  ApplicationMailer.notification(user, question).deliver
17
17
  end
data/lib/rostra/engine.rb CHANGED
@@ -8,7 +8,7 @@ module Rostra
8
8
  require 'thumbs_up'
9
9
  require 'acts_as_commentable'
10
10
  require 'impressionist'
11
- require 'will_paginate'
11
+ require 'kaminari'
12
12
  require_relative 'email_notifier'
13
13
 
14
14
  isolate_namespace Rostra
@@ -1,3 +1,3 @@
1
1
  module Rostra
2
- VERSION = "0.0.15"
2
+ VERSION = "0.0.16"
3
3
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: rostra
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.15
5
+ version: 0.0.16
6
6
  platform: ruby
7
7
  authors:
8
8
  - Cory Schires
@@ -113,7 +113,7 @@ dependencies:
113
113
  type: :runtime
114
114
  version_requirements: *id009
115
115
  - !ruby/object:Gem::Dependency
116
- name: will_paginate
116
+ name: kaminari
117
117
  prerelease: false
118
118
  requirement: &id010 !ruby/object:Gem::Requirement
119
119
  none: false
@@ -235,7 +235,9 @@ files:
235
235
  - app/assets/images/rostra/anonymous_avatar.png
236
236
  - app/assets/images/rostra/vote_arrows.png
237
237
  - app/assets/javascripts/rostra/application.js
238
+ - app/assets/javascripts/rostra/jquery.autoSuggest.js
238
239
  - app/assets/javascripts/rostra/jquery.jeditable.js
240
+ - app/assets/javascripts/rostra/rails.validations.custom.js
239
241
  - app/assets/javascripts/rostra/tiny_mce/jquery.tinymce.js
240
242
  - app/assets/javascripts/rostra/tiny_mce/langs/en.js
241
243
  - app/assets/javascripts/rostra/tiny_mce/plugins/advhr/css/advhr.css
@@ -490,6 +492,7 @@ files:
490
492
  - app/assets/javascripts/rostra/tiny_mce/utils/mctabs.js
491
493
  - app/assets/javascripts/rostra/tiny_mce/utils/validate.js
492
494
  - app/assets/stylesheets/rostra/application.css
495
+ - app/assets/stylesheets/rostra/autoSuggest.css
493
496
  - app/assets/stylesheets/rostra/buttons.css
494
497
  - app/assets/stylesheets/rostra/flash_messages.css
495
498
  - app/assets/stylesheets/rostra/forms.css
@@ -541,6 +544,7 @@ files:
541
544
  - db/migrate/20111012020025_create_comments.rb
542
545
  - db/migrate/20111016021105_create_impressions_table.rb
543
546
  - db/migrate/20111019162723_create_rostra_question_followings.rb
547
+ - db/migrate/20111024223022_add_send_email_notifications_to_question_followings.rb
544
548
  - lib/generators/rostra/install_generator.rb
545
549
  - lib/generators/rostra/templates/app/helpers/rostra/application_helper.rb
546
550
  - lib/generators/rostra/templates/config/initializers/rostra.rb