fae-rails 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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