rostra 0.0.15 → 0.0.16

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.
@@ -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