fae-rails 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/app/assets/javascripts/fae/form/_ajax.js +21 -1
  4. data/app/assets/javascripts/fae/form/_cancel.js +1 -0
  5. data/app/assets/javascripts/fae/form/_validator.js +22 -12
  6. data/app/assets/javascripts/fae/form/inputs/_color.js +2 -1
  7. data/app/assets/javascripts/fae/vendor/jqColorPicker.min.js +0 -1
  8. data/app/controllers/fae/application_controller.rb +15 -1
  9. data/app/controllers/fae/users_controller.rb +8 -0
  10. data/app/helpers/fae/form_helper.rb +13 -4
  11. data/app/helpers/fae/view_helper.rb +76 -31
  12. data/app/models/concerns/fae/user_concern.rb +10 -1
  13. data/app/models/fae/static_page.rb +7 -2
  14. data/app/models/fae/user.rb +2 -3
  15. data/app/uploaders/fae/file_uploader.rb +1 -1
  16. data/app/uploaders/fae/image_uploader.rb +1 -1
  17. data/app/views/fae/application/_file_uploader.html.slim +4 -1
  18. data/app/views/fae/application/_global_search_results.html.slim +1 -1
  19. data/app/views/fae/application/_header.slim +8 -8
  20. data/app/views/fae/application/_markdown_helper.slim +17 -17
  21. data/app/views/fae/application/_mobilenav.slim +6 -6
  22. data/app/views/fae/application/_user_log.html.slim +2 -2
  23. data/app/views/fae/images/_image_uploader.html.slim +1 -1
  24. data/app/views/fae/options/_form.html.slim +6 -6
  25. data/app/views/fae/pages/activity_log.html.slim +11 -11
  26. data/app/views/fae/pages/disabled_environment.html.slim +2 -2
  27. data/app/views/fae/pages/error404.html.slim +5 -3
  28. data/app/views/fae/pages/home.html.slim +9 -8
  29. data/app/views/fae/setup/first_user.html.slim +3 -3
  30. data/app/views/fae/shared/_form_header.html.slim +3 -3
  31. data/app/views/fae/shared/_index_header.html.slim +3 -2
  32. data/app/views/fae/shared/_nested_table.html.slim +1 -1
  33. data/app/views/fae/shared/_recent_changes.html.slim +6 -6
  34. data/app/views/fae/shared/_shared_nested_table.html.slim +1 -1
  35. data/app/views/fae/static_pages/index.html.slim +2 -2
  36. data/app/views/fae/users/_form.html.slim +8 -12
  37. data/app/views/fae/users/index.html.slim +6 -6
  38. data/config/locales/devise.cs.yml +59 -0
  39. data/config/locales/fae.cs.yml +125 -0
  40. data/config/locales/fae.en.yml +109 -0
  41. data/config/locales/fae.zh-CN.yml +109 -0
  42. data/lib/fae/version.rb +1 -1
  43. data/lib/generators/fae/base_generator.rb +46 -3
  44. data/lib/generators/fae/nested_index_scaffold_generator.rb +1 -0
  45. data/lib/generators/fae/nested_scaffold_generator.rb +1 -0
  46. data/lib/generators/fae/page_generator.rb +8 -0
  47. data/lib/generators/fae/scaffold_generator.rb +1 -0
  48. data/lib/generators/fae/templates/graphql/graphql_page_type.rb +17 -0
  49. data/lib/generators/fae/templates/graphql/graphql_type.rb +13 -0
  50. metadata +9 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 792dd37ac209ab8e11c320ca46d8fa889aa7b076
4
- data.tar.gz: 2b06e7346546949a0537f25f0e2c20245c536036
3
+ metadata.gz: 64efa885d567372d041fc98081e6074e37adb03a
4
+ data.tar.gz: 9945edf6077be0c1ef698a593da299c422baeb60
5
5
  SHA512:
6
- metadata.gz: 52ceefb35b9b05867733ca0f125657b474a9517c1df2bcd5706ca51dd76c81bb14458062fa15ede894c9073623e8f43f336e96e09a2291567918a1112c2cdb69
7
- data.tar.gz: 963568ce30f5c580bdb33eefb24639145ea4b2c1f5a271dfb08ae85a63e49d9f22efbde7e0c0e18dbf634ee7fdcc4c0fccec1831c29745fe68cf0297ff89d28a
6
+ metadata.gz: ab50aeecbc77980ccb20b73c742ef4043b74319b658a4e01f5a88432f538f95896e18bdf87cfdfee55331026d03283315e4b6ff1f1f8738274bc869a458f3bc9
7
+ data.tar.gz: 48c3aae1f2cde1deb1405161f4942328c417733bf2832bac73fc4ca6967d96c30a0a8dfe994121c19e14631bdaabad28f721c9d4be82bdb099343bfff04f1947
data/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  Like many Rails CMS engines, Fae delivers all the basics to get you up and running quickly: authentication, authorization, a sleek UI, form helpers, image processing and workflows. But unlike other engines, Fae's generated models, controllers, and views are built to customize and scale.
9
9
 
10
- Fae supports Rails 4.1 to 5.0. We are currently working on Rails 5.1 support.
10
+ Fae 2.0 supports Rails 5.0 to 5.2, support for Rails 4.x is deprecated as of Fae 2.0.
11
11
 
12
12
  ## Installation
13
13
 
@@ -61,6 +61,7 @@ https://www.faecms.com/documentation
61
61
 
62
62
  ### Tutorials
63
63
 
64
+ * [Setting Up GraphQL with Fae](docs/tutorials/graphql_support.md)
64
65
  * [Setting Up Images and Files](docs/tutorials/image_and_files.md)
65
66
  * [Adding Dynamic Relationships to Pages](docs/tutorials/dynamic_relationships_to_pages.md)
66
67
  * [Adding Conditional Validations](docs/tutorials/conditional_validations.md)
@@ -84,6 +85,7 @@ https://www.faecms.com/documentation
84
85
 
85
86
  * [Running Fae Locally](docs/contributing/local_setup.md)
86
87
  * [Fae Standards](docs/contributing/standards.md)
88
+ * [Share Your Creation](docs/contributing/share_your_creation.md)
87
89
 
88
90
  ## [Upgrading](docs/upgrading/index.md)
89
91
 
@@ -10,6 +10,7 @@ Fae.form.ajax = {
10
10
  init: function() {
11
11
  this.$addedit_form = $('.js-addedit-form, .js-index-addedit-form');
12
12
  this.$filter_form = $('.js-filter-form');
13
+ this.$nested_form = $('.nested-form');
13
14
 
14
15
  this.addEditLinks();
15
16
  this.addEditSubmission();
@@ -66,6 +67,9 @@ Fae.form.ajax = {
66
67
  $wrapper.find('.input.file').fileinputer();
67
68
  }
68
69
 
70
+ // Bind validation to nested form fields added by AJAX
71
+ Fae.form.validator.bindValidationEvents(this.$nested_form);
72
+
69
73
  // Reinitialize form elements
70
74
  Fae.form.dates.initDatepicker();
71
75
  Fae.form.dates.initDateRangePicker();
@@ -73,7 +77,12 @@ Fae.form.ajax = {
73
77
  Fae.form.slugger.addListener();
74
78
  Fae.form.validator.length_counter.init();
75
79
  Fae.form.text.initMarkdown();
80
+ Fae.form.text.initHTML();
76
81
  Fae.form.checkbox.setCheckboxAsActive();
82
+ Fae.form.select.init();
83
+
84
+ // validate nested form fields on submit
85
+ Fae.form.validator.formValidate(this.$nested_form);
77
86
 
78
87
  $wrapper.find('.hint').hinter();
79
88
  });
@@ -126,7 +135,7 @@ Fae.form.ajax = {
126
135
  } else if ($html.hasClass('nested-form')) {
127
136
 
128
137
  // we're returning the form due to an error, just replace the form
129
- $this.find( '.nested-form' ).replaceWith(data);
138
+ $this.find('.nested-form' ).replaceWith($html);
130
139
  $this.find('.select select').fae_chosen();
131
140
  $this.find('.input.file').fileinputer();
132
141
 
@@ -136,6 +145,7 @@ Fae.form.ajax = {
136
145
  Fae.form.validator.length_counter.init();
137
146
  Fae.form.checkbox.setCheckboxAsActive();
138
147
  Fae.form.text.initMarkdown();
148
+ Fae.form.text.initHTML();
139
149
 
140
150
  FCH.smoothScroll($this.find('.js-addedit-form-wrapper'), 500, 100, 120);
141
151
  }
@@ -289,6 +299,16 @@ Fae.form.ajax = {
289
299
  htmlListeners: function() {
290
300
  $('#js-main-content, .login-form > form')
291
301
 
302
+ /**
303
+ * For the delete button on file input
304
+ */
305
+ .on('click', '.js-file-clear', function(e) {
306
+ e.preventDefault();
307
+ var $parent = $(this).parent();
308
+ $parent.next().show();
309
+ $parent.hide();
310
+ })
311
+
292
312
  /**
293
313
  * For the yes/no slider
294
314
  */
@@ -35,4 +35,5 @@ Fae.form.cancel = {
35
35
 
36
36
  $('form').on('change', 'input, textarea, select', updateCancel);
37
37
  }
38
+
38
39
  };
@@ -26,10 +26,13 @@ Fae.form.validator = {
26
26
  /**
27
27
  * Validate the entire form on submit and stop it if the form is invalid
28
28
  */
29
- formValidate: function () {
29
+ formValidate: function ($scope) {
30
30
  var _this = this;
31
31
 
32
- FCH.$document.on('submit', 'form:not([data-remote=true])', function (e) {
32
+ if (typeof($scope) === 'undefined'){
33
+ $scope = FCH.$document;
34
+ }
35
+ $scope.on('submit', 'form:not([data-remote=true])', function (e) {
33
36
  var $this = $(this);
34
37
 
35
38
  if ($this.data('passed_validation') !== 'true') {
@@ -58,7 +61,7 @@ Fae.form.validator = {
58
61
  }
59
62
  });
60
63
 
61
- _this.testValidation($this);
64
+ _this.testValidation($this, $scope);
62
65
 
63
66
  }
64
67
 
@@ -69,7 +72,7 @@ Fae.form.validator = {
69
72
  * Tests a forms validation after all validation checks have responded
70
73
  * Polls validations responses every 50ms to allow uniqueness AJAX calls to complete
71
74
  */
72
- testValidation: function($this) {
75
+ testValidation: function($this, $scope) {
73
76
  var _this = this;
74
77
  _this.validation_test_count++;
75
78
 
@@ -84,9 +87,11 @@ Fae.form.validator = {
84
87
 
85
88
  $this.submit();
86
89
  } else {
87
- // otherwise scroll to the top to display alerts
90
+ // otherwise scroll to the top to display alerts (unless in a nested form scope)
88
91
  Fae.navigation.language.checkForHiddenErrors();
89
- FCH.smoothScroll($('#js-main-header'), 500, 100, 0);
92
+ if (typeof($scope) === 'undefined') {
93
+ FCH.smoothScroll($('#js-main-header'), 500, 100, 0);
94
+ }
90
95
 
91
96
  if ($(".field_with_errors").length) {
92
97
  $('.alert').slideDown('fast').delay(3000).slideUp('fast');
@@ -108,10 +113,14 @@ Fae.form.validator = {
108
113
  /**
109
114
  * Bind validation events based on input type
110
115
  */
111
- bindValidationEvents: function () {
116
+ bindValidationEvents: function ($scope) {
112
117
  var _this = this;
113
118
 
114
- $('[data-validate]').each(function () {
119
+ if (typeof($scope) === 'undefined'){
120
+ $scope = $('body');
121
+ }
122
+
123
+ $scope.find('[data-validate]').each(function () {
115
124
  var $this = $(this);
116
125
 
117
126
  if ($this.data('validate').length) {
@@ -264,12 +273,13 @@ Fae.form.validator = {
264
273
  // if the kind matches, remove it from the array
265
274
  if (validations[i]['kind'] === kind) {
266
275
  validations.splice(i, 1);
276
+ i--;
277
+ } else {
278
+ // otherwise convert JSON back to a string
279
+ validations[i] = JSON.stringify(validations[i]);
267
280
  }
268
-
269
- // convert JSON back to a string
270
- validations[i] = JSON.stringify(validations[i]);
271
281
  }
272
- $field.attr('data-validate', '[' + validations + ']');
282
+ $field.data('validate', '[' + validations + ']');
273
283
  },
274
284
 
275
285
  /**
@@ -23,8 +23,9 @@ Fae.form.color = {
23
23
  var inputOffset = $elm.offset().top;
24
24
  var distanceToBottom = $(document).height() - inputOffset;
25
25
  var top = distanceToBottom <= this.$UI._height ? 414 : inputOffset + $elm.innerHeight();
26
+ var left = $elm.offset().left;
26
27
 
27
- return { left: 30, top: top }
28
+ return { left: left, top: top }
28
29
  },
29
30
  renderCallback: function($elm, toggled) {
30
31
  var colors = this.color.colors;
@@ -1,4 +1,3 @@
1
1
  /*! tinyColorPicker - v1.1.1 2016-07-15 */
2
2
 
3
3
  !function(a,b){"object"==typeof exports?module.exports=b(a):"function"==typeof define&&define.amd?define("colors",[],function(){return b(a)}):a.Colors=b(a)}(this,function(a,b){"use strict";function c(a,c,d,f,g){if("string"==typeof c){var c=v.txt2color(c);d=c.type,p[d]=c[d],g=g!==b?g:c.alpha}else if(c)for(var h in c)a[d][h]=k(c[h]/l[d][h][1],0,1);return g!==b&&(a.alpha=k(+g,0,1)),e(d,f?a:b)}function d(a,b,c){var d=o.options.grey,e={};return e.RGB={r:a.r,g:a.g,b:a.b},e.rgb={r:b.r,g:b.g,b:b.b},e.alpha=c,e.equivalentGrey=n(d.r*a.r+d.g*a.g+d.b*a.b),e.rgbaMixBlack=i(b,{r:0,g:0,b:0},c,1),e.rgbaMixWhite=i(b,{r:1,g:1,b:1},c,1),e.rgbaMixBlack.luminance=h(e.rgbaMixBlack,!0),e.rgbaMixWhite.luminance=h(e.rgbaMixWhite,!0),o.options.customBG&&(e.rgbaMixCustom=i(b,o.options.customBG,c,1),e.rgbaMixCustom.luminance=h(e.rgbaMixCustom,!0),o.options.customBG.luminance=h(o.options.customBG,!0)),e}function e(a,b){var c,e,k,q=b||p,r=v,s=o.options,t=l,u=q.RND,w="",x="",y={hsl:"hsv",rgb:a},z=u.rgb;if("alpha"!==a){for(var A in t)if(!t[A][A]){a!==A&&(x=y[A]||"rgb",q[A]=r[x+"2"+A](q[x])),u[A]||(u[A]={}),c=q[A];for(w in c)u[A][w]=n(c[w]*t[A][w][1])}z=u.rgb,q.HEX=r.RGB2HEX(z),q.equivalentGrey=s.grey.r*q.rgb.r+s.grey.g*q.rgb.g+s.grey.b*q.rgb.b,q.webSave=e=f(z,51),q.webSmart=k=f(z,17),q.saveColor=z.r===e.r&&z.g===e.g&&z.b===e.b?"web save":z.r===k.r&&z.g===k.g&&z.b===k.b?"web smart":"",q.hueRGB=v.hue2RGB(q.hsv.h),b&&(q.background=d(z,q.rgb,q.alpha))}var B,C,D,E=q.rgb,F=q.alpha,G="luminance",H=q.background;return B=i(E,{r:0,g:0,b:0},F,1),B[G]=h(B,!0),q.rgbaMixBlack=B,C=i(E,{r:1,g:1,b:1},F,1),C[G]=h(C,!0),q.rgbaMixWhite=C,s.customBG&&(D=i(E,H.rgbaMixCustom,F,1),D[G]=h(D,!0),D.WCAG2Ratio=j(D[G],H.rgbaMixCustom[G]),q.rgbaMixBGMixCustom=D,D.luminanceDelta=m.abs(D[G]-H.rgbaMixCustom[G]),D.hueDelta=g(H.rgbaMixCustom,D,!0)),q.RGBLuminance=h(z),q.HUELuminance=h(q.hueRGB),s.convertCallback&&s.convertCallback(q,a),q}function f(a,b){var c={},d=0,e=b/2;for(var f in a)d=a[f]%b,c[f]=a[f]+(d>e?b-d:-d);return c}function g(a,b,c){return(m.max(a.r-b.r,b.r-a.r)+m.max(a.g-b.g,b.g-a.g)+m.max(a.b-b.b,b.b-a.b))*(c?255:1)/765}function h(a,b){for(var c=b?1:255,d=[a.r/c,a.g/c,a.b/c],e=o.options.luminance,f=d.length;f--;)d[f]=d[f]<=.03928?d[f]/12.92:m.pow((d[f]+.055)/1.055,2.4);return e.r*d[0]+e.g*d[1]+e.b*d[2]}function i(a,c,d,e){var f={},g=d!==b?d:1,h=e!==b?e:1,i=g+h*(1-g);for(var j in a)f[j]=(a[j]*g+c[j]*h*(1-g))/i;return f.a=i,f}function j(a,b){var c=1;return c=a>=b?(a+.05)/(b+.05):(b+.05)/(a+.05),n(100*c)/100}function k(a,b,c){return a>c?c:b>a?b:a}var l={rgb:{r:[0,255],g:[0,255],b:[0,255]},hsv:{h:[0,360],s:[0,100],v:[0,100]},hsl:{h:[0,360],s:[0,100],l:[0,100]},alpha:{alpha:[0,1]},HEX:{HEX:[0,16777215]}},m=a.Math,n=m.round,o={},p={},q={r:.298954,g:.586434,b:.114612},r={r:.2126,g:.7152,b:.0722},s=function(a){this.colors={RND:{}},this.options={color:"rgba(0,0,0,0)",grey:q,luminance:r,valueRanges:l},t(this,a||{})},t=function(a,d){var e,f=a.options;u(a);for(var g in d)d[g]!==b&&(f[g]=d[g]);e=f.customBG,f.customBG="string"==typeof e?v.txt2color(e).rgb:e,p=c(a.colors,f.color,b,!0)},u=function(a){o!==a&&(o=a,p=a.colors)};s.prototype.setColor=function(a,d,f){return u(this),a?c(this.colors,a,d,b,f):(f!==b&&(this.colors.alpha=k(f,0,1)),e(d))},s.prototype.setCustomBackground=function(a){return u(this),this.options.customBG="string"==typeof a?v.txt2color(a).rgb:a,c(this.colors,b,"rgb")},s.prototype.saveAsBackground=function(){return u(this),c(this.colors,b,"rgb",!0)},s.prototype.toString=function(a,b){return v.color2text((a||"rgb").toLowerCase(),this.colors,b)};var v={txt2color:function(a){var b={},c=a.replace(/(?:#|\)|%)/g,"").split("("),d=(c[1]||"").split(/,\s*/),e=c[1]?c[0].substr(0,3):"rgb",f="";if(b.type=e,b[e]={},c[1])for(var g=3;g--;)f=e[g]||e.charAt(g),b[e][f]=+d[g]/l[e][f][1];else b.rgb=v.HEX2rgb(c[0]);return b.alpha=d[3]?+d[3]:1,b},color2text:function(a,b,c){var d=c!==!1&&n(100*b.alpha)/100,e="number"==typeof d&&c!==!1&&(c||1!==d),f=b.RND.rgb,g=b.RND.hsl,h="hex"===a&&e,i="hex"===a&&!h,j="rgb"===a||h,k=j?f.r+", "+f.g+", "+f.b:i?"#"+b.HEX:g.h+", "+g.s+"%, "+g.l+"%";return i?k:(h?"rgb":a)+(e?"a":"")+"("+k+(e?", "+d:"")+")"},RGB2HEX:function(a){return((a.r<16?"0":"")+a.r.toString(16)+(a.g<16?"0":"")+a.g.toString(16)+(a.b<16?"0":"")+a.b.toString(16)).toUpperCase()},HEX2rgb:function(a){return a=a.split(""),{r:+("0x"+a[0]+a[a[3]?1:0])/255,g:+("0x"+a[a[3]?2:1]+(a[3]||a[1]))/255,b:+("0x"+(a[4]||a[2])+(a[5]||a[2]))/255}},hue2RGB:function(a){var b=6*a,c=~~b%6,d=6===b?0:b-c;return{r:n(255*[1,1-d,0,0,d,1][c]),g:n(255*[d,1,1,1-d,0,0][c]),b:n(255*[0,0,d,1,1,1-d][c])}},rgb2hsv:function(a){var b,c,d,e=a.r,f=a.g,g=a.b,h=0;return g>f&&(f=g+(g=f,0),h=-1),c=g,f>e&&(e=f+(f=e,0),h=-2/6-h,c=m.min(f,g)),b=e-c,d=e?b/e:0,{h:1e-15>d?p&&p.hsl&&p.hsl.h||0:b?m.abs(h+(f-g)/(6*b)):0,s:e?b/e:p&&p.hsv&&p.hsv.s||0,v:e}},hsv2rgb:function(a){var b=6*a.h,c=a.s,d=a.v,e=~~b,f=b-e,g=d*(1-c),h=d*(1-f*c),i=d*(1-(1-f)*c),j=e%6;return{r:[d,h,g,g,i,d][j],g:[i,d,d,h,g,g][j],b:[g,g,i,d,d,h][j]}},hsv2hsl:function(a){var b=(2-a.s)*a.v,c=a.s*a.v;return c=a.s?1>b?b?c/b:0:c/(2-b):0,{h:a.h,s:a.v||c?c:p&&p.hsl&&p.hsl.s||0,l:b/2}},rgb2hsl:function(a,b){var c=v.rgb2hsv(a);return v.hsv2hsl(b?c:p.hsv=c)},hsl2rgb:function(a){var b=6*a.h,c=a.s,d=a.l,e=.5>d?d*(1+c):d+c-c*d,f=d+d-e,g=e?(e-f)/e:0,h=~~b,i=b-h,j=e*g*i,k=f+j,l=e-j,m=h%6;return{r:[e,l,f,f,k,e][m],g:[k,e,e,l,f,f][m],b:[f,f,k,e,e,l][m]}}};return s}),function(a,b){"object"==typeof exports?module.exports=b(a,require("jquery"),require("colors")):"function"==typeof define&&define.amd?define(["jquery","colors"],function(c,d){return b(a,c,d)}):b(a,a.jQuery,a.Colors)}(this,function(a,b,c,d){"use strict";function e(a){return a.value||a.getAttribute("value")||b(a).css("background-color")||"#FFF"}function f(a){return a=a.originalEvent&&a.originalEvent.touches?a.originalEvent.touches[0]:a,a.originalEvent?a.originalEvent:a}function g(a){return b(a.find(r.doRender)[0]||a[0])}function h(c){var d=b(this),f=d.offset(),h=b(a),k=r.gap;c?(s=g(d),s._colorMode=s.data("colorMode"),p.$trigger=d,(t||i()).css(r.positionCallback.call(p,d)||{left:(t._left=f.left)-((t._left+=t._width-(h.scrollLeft()+h.width()))+k>0?t._left+k:0),top:(t._top=f.top+d.outerHeight())-((t._top+=t._height-(h.scrollTop()+h.height()))+k>0?t._top+k:0)}).show(r.animationSpeed,function(){c!==!0&&(y.toggle(!!r.opacity)._width=y.width(),v._width=v.width(),v._height=v.height(),u._height=u.height(),q.setColor(e(s[0])),n(!0))}).off(".tcp").on(D,".cp-xy-slider,.cp-z-slider,.cp-alpha",j)):p.$trigger&&b(t).hide(r.animationSpeed,function(){n(!1),p.$trigger=null}).off(".tcp")}function i(){return b("head").append('<style type="text/css" id="tinyColorPickerStyles">'+(r.css||I)+(r.cssAddon||"")+"</style>"),b(H).css({margin:r.margin}).appendTo("body").show(0,function(){p.$UI=t=b(this),F=r.GPU&&t.css("perspective")!==d,u=b(".cp-z-slider",this),v=b(".cp-xy-slider",this),w=b(".cp-xy-cursor",this),x=b(".cp-z-cursor",this),y=b(".cp-alpha",this),z=b(".cp-alpha-cursor",this),r.buildCallback.call(p,t),t.prepend("<div>").children().eq(0).css("width",t.children().eq(0).width()),t._width=this.offsetWidth,t._height=this.offsetHeight}).hide()}function j(a){var c=this.className.replace(/cp-(.*?)(?:\s*|$)/,"$1").replace("-","_");(a.button||a.which)>1||(a.preventDefault&&a.preventDefault(),a.returnValue=!1,s._offset=b(this).offset(),(c="xy_slider"===c?k:"z_slider"===c?l:m)(a),n(),A.on(E,function(){A.off(".tcp")}).on(C,function(a){c(a),n()}))}function k(a){var b=f(a),c=b.pageX-s._offset.left,d=b.pageY-s._offset.top;q.setColor({s:c/v._width*100,v:100-d/v._height*100},"hsv")}function l(a){var b=f(a).pageY-s._offset.top;q.setColor({h:360-b/u._height*360},"hsv")}function m(a){var b=f(a).pageX-s._offset.left,c=b/y._width;q.setColor({},"rgb",c)}function n(a){var b=q.colors,c=b.hueRGB,e=(b.RND.rgb,b.RND.hsl,r.dark),f=r.light,g=q.toString(s._colorMode,r.forceAlpha),h=b.HUELuminance>.22?e:f,i=b.rgbaMixBlack.luminance>.22?e:f,j=(1-b.hsv.h)*u._height,k=b.hsv.s*v._width,l=(1-b.hsv.v)*v._height,m=b.alpha*y._width,n=F?"translate3d":"",p=s[0].value,t=s[0].hasAttribute("value")&&""===p&&a!==d;v._css={backgroundColor:"rgb("+c.r+","+c.g+","+c.b+")"},w._css={transform:n+"("+k+"px, "+l+"px, 0)",left:F?"":k,top:F?"":l,borderColor:b.RGBLuminance>.22?e:f},x._css={transform:n+"(0, "+j+"px, 0)",top:F?"":j,borderColor:"transparent "+h},y._css={backgroundColor:"#"+b.HEX},z._css={transform:n+"("+m+"px, 0, 0)",left:F?"":m,borderColor:i+" transparent"},s._css={backgroundColor:t?"":g,color:t?"":b.rgbaMixBGMixCustom.luminance>.22?e:f},s.text=t?"":p!==g?g:"",a!==d?o(a):G(o)}function o(a){v.css(v._css),w.css(w._css),x.css(x._css),y.css(y._css),z.css(z._css),r.doRender&&s.css(s._css),s.text&&s.val(s.text),r.renderCallback.call(p,s,"boolean"==typeof a?a:d)}var p,q,r,s,t,u,v,w,x,y,z,A=b(document),B=b(),C="touchmove.tcp mousemove.tcp pointermove.tcp",D="touchstart.tcp mousedown.tcp pointerdown.tcp",E="touchend.tcp mouseup.tcp pointerup.tcp",F=!1,G=a.requestAnimationFrame||a.webkitRequestAnimationFrame||function(a){a()},H='<div class="cp-color-picker"><div class="cp-z-slider"><div class="cp-z-cursor"></div></div><div class="cp-xy-slider"><div class="cp-white"></div><div class="cp-xy-cursor"></div></div><div class="cp-alpha"><div class="cp-alpha-cursor"></div></div></div>',I=".cp-color-picker{position:absolute;overflow:hidden;padding:6px 6px 0;background-color:#444;color:#bbb;font-family:Arial,Helvetica,sans-serif;font-size:12px;font-weight:400;cursor:default;border-radius:5px}.cp-color-picker>div{position:relative;overflow:hidden}.cp-xy-slider{float:left;height:128px;width:128px;margin-bottom:6px;background:linear-gradient(to right,#FFF,rgba(255,255,255,0))}.cp-white{height:100%;width:100%;background:linear-gradient(rgba(0,0,0,0),#000)}.cp-xy-cursor{position:absolute;top:0;width:10px;height:10px;margin:-5px;border:1px solid #fff;border-radius:100%;box-sizing:border-box}.cp-z-slider{float:right;margin-left:6px;height:128px;width:20px;background:linear-gradient(red 0,#f0f 17%,#00f 33%,#0ff 50%,#0f0 67%,#ff0 83%,red 100%)}.cp-z-cursor{position:absolute;margin-top:-4px;width:100%;border:4px solid #fff;border-color:transparent #fff;box-sizing:border-box}.cp-alpha{clear:both;width:100%;height:16px;margin:6px 0;background:linear-gradient(to right,#444,rgba(0,0,0,0))}.cp-alpha-cursor{position:absolute;margin-left:-4px;height:100%;border:4px solid #fff;border-color:#fff transparent;box-sizing:border-box}",J=function(a){q=this.color=new c(a),r=q.options,p=this};J.prototype={render:n,toggle:h},b.fn.colorPicker=function(c){var d=this,f=function(){};return c=b.extend({animationSpeed:150,GPU:!0,doRender:!0,customBG:"#FFF",opacity:!0,renderCallback:f,buildCallback:f,positionCallback:f,body:document.body,scrollResize:!0,gap:4,dark:"#222",light:"#DDD"},c),!p&&c.scrollResize&&b(a).on("resize.tcp scroll.tcp",function(){p.$trigger&&p.toggle.call(p.$trigger[0],!0)}),B=B.add(this),this.colorPicker=p||new J(c),this.options=c,b(c.body).off(".tcp").on(D,function(a){-1===B.add(t).add(b(t).find(a.target)).index(a.target)&&h()}),this.on("focusin.tcp click.tcp",function(a){p.color.options=b.extend(p.color.options,r=d.options),h.call(this,a)}).on("change.tcp",function(){q.setColor(this.value||"#FFF"),d.colorPicker.render(!0)}).each(function(){var a=e(this),d=a.split("("),f=g(b(this));f.data("colorMode",d[1]?d[0].substr(0,3):"HEX").attr("readonly",r.preventFocus),c.doRender&&f.css({"background-color":a,color:function(){return q.setColor(a).rgbaMixBGMixCustom.luminance>.22?c.dark:c.light}})})},b.fn.colorPicker.destroy=function(){b("*").off(".tcp"),p.toggle(!1),B=b()}});
4
- //# sourceMappingURL=jqColorPicker.js.map
@@ -20,8 +20,22 @@ module Fae
20
20
 
21
21
  private
22
22
 
23
+ # defines the locale used to translate the Fae interface
23
24
  def set_locale
24
- I18n.locale = :en
25
+ if I18n.available_locales.include?(language_from_browser)
26
+ language_from_browser
27
+ else
28
+ :en
29
+ end
30
+ end
31
+
32
+ # parse HTTP_ACCEPT_LANGUAGE and return an array of language codes
33
+ # ex: ["en-US", "en", "zh-CN", "zh", "cs"]
34
+ # then grab the first and convert to symbol
35
+ def language_from_browser
36
+ if request.env['HTTP_ACCEPT_LANGUAGE'].present?
37
+ request.env['HTTP_ACCEPT_LANGUAGE'].scan(/[a-z-]{2,5}/i).first.try(:to_sym)
38
+ end
25
39
  end
26
40
 
27
41
  def check_disabled_environment
@@ -3,6 +3,7 @@ module Fae
3
3
  before_action :admin_only, except: [:settings, :update]
4
4
  before_action :set_user, only: [:show, :edit, :update, :destroy]
5
5
  before_action :set_role_collection, except: [:index, :destroy]
6
+ before_action :set_index_path, only: [:settings, :new, :edit]
6
7
 
7
8
  def index
8
9
  @users = current_user.super_admin? ? Fae::User.all : Fae::User.public_users
@@ -17,6 +18,8 @@ module Fae
17
18
 
18
19
  def settings
19
20
  @user = current_user
21
+ # set index path to dashboard
22
+ @index_path = root_path
20
23
  end
21
24
 
22
25
  def create
@@ -67,5 +70,10 @@ module Fae
67
70
  params.require(:user).permit(:email, :first_name, :last_name, :password, :password_confirmation)
68
71
  end
69
72
  end
73
+
74
+ def set_index_path
75
+ # @index_path determines form's cancel btn path
76
+ @index_path = users_path
77
+ end
70
78
  end
71
79
  end
@@ -122,7 +122,7 @@ module Fae
122
122
  options[:helper_text] = attempt_common_helper_text(attribute) if options[:helper_text].blank?
123
123
 
124
124
  attribute_name = options[:as].to_s == 'hidden' ? '' : attribute.to_s.titleize
125
- label = options[:label] || attribute_name
125
+ label = options[:label] || label_translation(attribute) || attribute_name
126
126
  if options[:markdown_supported].present? || options[:helper_text].present?
127
127
  label += content_tag :h6, class: 'helper_text' do
128
128
  concat(options[:helper_text]) if options[:helper_text].present?
@@ -134,6 +134,15 @@ module Fae
134
134
  options[:hint] = hint.html_safe if hint.present?
135
135
  end
136
136
 
137
+ def label_translation(attribute)
138
+ try_translation attribute, 'fae.form.attribute'
139
+ end
140
+
141
+ def try_translation(item, translation_path)
142
+ translation = t("#{translation_path}.#{item}")
143
+ translation =~ /translation_missing/ ? nil : translation
144
+ end
145
+
137
146
  def is_attribute_or_association?(f, attribute)
138
147
  f.object.has_attribute?(attribute) || is_association?(f, attribute)
139
148
  end
@@ -192,7 +201,7 @@ module Fae
192
201
 
193
202
  # sets default prompt for pulldowns
194
203
  def set_prompt(f, attribute, options)
195
- options[:prompt] = 'Select One' if is_association?(f, attribute) && f.object.class.reflect_on_association(attribute).macro == :belongs_to && options[:prompt].nil? && !options[:two_pane]
204
+ options[:prompt] = 'None' if is_association?(f, attribute) && f.object.class.reflect_on_association(attribute).macro == :belongs_to && options[:prompt].nil? && !options[:two_pane]
196
205
  end
197
206
 
198
207
  # removes language suffix from label and adds data attr for languange nav
@@ -228,9 +237,9 @@ module Fae
228
237
  def attempt_common_helper_text(attribute)
229
238
  case attribute
230
239
  when :seo_title
231
- return 'A descriptive page title of ~50-65 characters. Displayed in search engine results.'
240
+ return t('fae.seo_title')
232
241
  when :seo_description
233
- return 'A helpful page summary of 320 characters or less. Displayed in search engine results.'
242
+ return t('fae.seo_description')
234
243
  else
235
244
  ''
236
245
  end
@@ -1,5 +1,5 @@
1
1
  module Fae
2
- module ViewHelper
2
+ module ViewHelper
3
3
 
4
4
  def fae_date_format(datetime, timezone = @option.time_zone)
5
5
  datetime.in_time_zone(timezone).strftime('%m/%d/%y') if is_date_or_time?(datetime)
@@ -71,47 +71,34 @@ module Fae
71
71
  end
72
72
 
73
73
  def fae_filter_form(options = {}, &block)
74
- options[:collection] ||= @items
75
- options[:action] ||= "#{@index_path}/filter"
76
- options[:title] ||= "Search #{@klass_humanized.pluralize.titleize}"
77
- options[:search] = true if options[:search].nil?
78
- options[:cookie_key] ||= false
79
-
74
+ options = prepare_options_for_filter_form options
80
75
  return if options[:collection].blank?
81
76
 
82
- form_hash = { class: 'js-filter-form table-filter-area' }
83
- form_hash['data-cookie-key'] = options[:cookie_key] if options[:cookie_key].present?
84
-
85
- filter_header = content_tag(:div, class: 'table-filter-header') do
86
- concat content_tag :h4, options[:title]
87
- concat filter_search_field if options[:search]
88
- end
89
-
90
- form_tag(options[:action], form_hash) do
91
- concat filter_header
77
+ form_tag(options[:action], prepare_form_filter_hash(options)) do
78
+ concat prepare_filter_header(options)
92
79
 
93
80
  if block_given?
94
81
  filter_group_wrapper = content_tag(:div, class: 'table-filter-group-wrapper') do
82
+ link_tag = content_tag(:a,
83
+ t('fae.reset_search'),
84
+ class: 'js-reset-btn button -small hidden',
85
+ href: '#')
95
86
  concat capture(&block)
96
- concat content_tag(:div, content_tag(:a, 'Reset Search', class: 'js-reset-btn button -small hidden', href: '#'), class: 'table-filter-group')
87
+ concat content_tag(:div,
88
+ link_tag,
89
+ class: 'table-filter-group')
97
90
  end
98
91
  end
99
-
100
92
  concat filter_group_wrapper
101
93
  # I know this `unless !` looks like it should be an `if` but it's definitely supposed to be `unless !`
102
- concat submit_tag 'Apply Filters', class: 'hidden' unless !options[:search]
94
+ unless !options[:search]
95
+ concat submit_tag t('fae.apply_filters'), class: 'hidden'
96
+ end
103
97
  end
104
98
  end
105
99
 
106
- def fae_filter_select(attribute, options = {})
107
- options[:label] ||= attribute.to_s.titleize
108
- options[:collection] ||= default_collection_from_attribute(attribute)
109
- options[:label_method] ||= :fae_display_field
110
- options[:placeholder] = "All #{options[:label].pluralize}" if options[:placeholder].nil?
111
- options[:options] ||= []
112
- options[:grouped_by] ||= nil
113
- options[:grouped_options] ||= []
114
-
100
+ def fae_filter_select(attribute, opts = {})
101
+ options = prepare_options_for_filter_select(attribute, opts)
115
102
  # grouped_by takes priority over grouped_options
116
103
  if options[:grouped_by].present?
117
104
  select_options = filter_generate_select_group(options[:collection], options[:grouped_by], options[:label_method])
@@ -122,7 +109,6 @@ module Fae
122
109
  select_options = options_for_select(options[:options]) if options[:options].present?
123
110
  end
124
111
 
125
-
126
112
  content_tag :div, class: 'table-filter-group' do
127
113
  concat label_tag "filter[#{attribute}]", options[:label]
128
114
  concat select_tag "filter[#{attribute}]", select_options, prompt: options[:placeholder]
@@ -144,7 +130,9 @@ module Fae
144
130
 
145
131
  def filter_search_field
146
132
  content_tag :div, class: 'table-filter-keyword-wrapper' do
147
- concat text_field_tag 'filter[search]', nil, placeholder: 'Search by Keyword'
133
+ concat text_field_tag('filter[search]',
134
+ nil,
135
+ placeholder: t('fae.keyword_search'))
148
136
  concat content_tag(:i, '', class: 'icon-search')
149
137
  end
150
138
  end
@@ -171,5 +159,62 @@ module Fae
171
159
  datetime.present? && ( datetime.kind_of?(Date) || datetime.kind_of?(Time) || datetime.kind_of?(ActiveSupport::TimeWithZone) )
172
160
  end
173
161
 
162
+ def try_placeholder_translation(attribute, path, label)
163
+ items = try_translation(attribute, path) || label
164
+ t('fae.all_items', items: items)
165
+ end
166
+
167
+ def prepare_options_for_filter_select(attribute, options)
168
+ options = preprare_label_for_filter_select attribute, options
169
+ options = prepare_placeholder_for_filter_select attribute, options
170
+ options[:collection] ||= default_collection_from_attribute(attribute)
171
+ options[:label_method] ||= :fae_display_field
172
+ options[:options] ||= []
173
+ options[:grouped_by] ||= nil
174
+ options[:grouped_options] ||= []
175
+ options
176
+ end
177
+
178
+ def preprare_label_for_filter_select(attribute, options)
179
+ options[:label] ||= try_translation(attribute.to_s,
180
+ options[:translation_path]) ||
181
+ attribute.to_s.titleize
182
+ options
183
+ end
184
+
185
+ def prepare_placeholder_for_filter_select(attribute, options)
186
+ if options[:placeholder].nil?
187
+ options[:placeholder] = try_placeholder_translation(
188
+ attribute.to_s.pluralize,
189
+ options[:translation_path],
190
+ options[:label].pluralize
191
+ )
192
+ end
193
+ options
194
+ end
195
+
196
+ def prepare_options_for_filter_form(options)
197
+ options[:collection] ||= @items
198
+ options[:action] ||= "#{@index_path}/filter"
199
+ options[:title] ||= t('fae.search',
200
+ search: @klass_humanized.pluralize.titleize)
201
+
202
+ options[:search] = true if options[:search].nil?
203
+ options[:cookie_key] ||= false
204
+ options
205
+ end
206
+
207
+ def prepare_filter_header(options)
208
+ content_tag(:div, class: 'table-filter-header') do
209
+ concat content_tag :h4, options[:title]
210
+ concat filter_search_field if options[:search]
211
+ end
212
+ end
213
+
214
+ def prepare_form_filter_hash(options)
215
+ form_hash = { class: 'js-filter-form table-filter-area' }
216
+ form_hash['data-cookie-key'] = options[:cookie_key] if options[:cookie_key].present?
217
+ form_hash
218
+ end
174
219
  end
175
220
  end