trestle 0.8.7 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/app/assets/javascripts/trestle/components/_datepicker.js +18 -3
  4. data/app/assets/javascripts/trestle/components/_dialog.js +5 -1
  5. data/app/assets/javascripts/trestle/components/_form.js +3 -1
  6. data/app/assets/javascripts/trestle/core/_l10n.js +23 -0
  7. data/app/assets/stylesheets/trestle/components/_avatar.scss +23 -0
  8. data/app/assets/stylesheets/trestle/components/_datepicker.scss +28 -0
  9. data/app/assets/stylesheets/trestle/components/_input-group.scss +18 -0
  10. data/app/helpers/trestle/avatar_helper.rb +7 -2
  11. data/app/helpers/trestle/debug_helper.rb +1 -1
  12. data/app/helpers/trestle/form_helper.rb +1 -1
  13. data/app/helpers/trestle/i18n_helper.rb +14 -0
  14. data/app/helpers/trestle/tab_helper.rb +2 -2
  15. data/app/helpers/trestle/url_helper.rb +37 -9
  16. data/app/views/layouts/trestle/admin.html.erb +1 -3
  17. data/app/views/trestle/application/_layout.html.erb +1 -1
  18. data/app/views/trestle/application/_tabs.html.erb +1 -1
  19. data/app/views/trestle/{application → flash}/_alert.html.erb +2 -2
  20. data/app/views/trestle/flash/_debug.html.erb +8 -0
  21. data/app/views/trestle/flash/_flash.html.erb +7 -0
  22. data/app/views/trestle/resource/edit.html.erb +5 -5
  23. data/app/views/trestle/resource/index.html.erb +2 -2
  24. data/app/views/trestle/resource/new.html.erb +4 -4
  25. data/app/views/trestle/resource/show.html.erb +5 -5
  26. data/app/views/trestle/shared/_sidebar.html.erb +2 -2
  27. data/config/locales/cs.rb +18 -0
  28. data/config/locales/cs.yml +95 -0
  29. data/config/locales/en.yml +43 -22
  30. data/config/locales/fr.yml +37 -22
  31. data/config/locales/nl.yml +37 -22
  32. data/config/locales/pl.yml +37 -22
  33. data/config/locales/pt-BR.yml +37 -22
  34. data/config/locales/zh-CN.rb +18 -0
  35. data/config/locales/zh-CN.yml +94 -0
  36. data/gemfiles/rails-5.2.gemfile +14 -0
  37. data/lib/trestle/adapters/adapter.rb +3 -3
  38. data/lib/trestle/admin.rb +30 -2
  39. data/lib/trestle/admin/builder.rb +17 -7
  40. data/lib/trestle/breadcrumb.rb +3 -1
  41. data/lib/trestle/configurable.rb +6 -0
  42. data/lib/trestle/configuration.rb +7 -1
  43. data/lib/trestle/engine.rb +5 -1
  44. data/lib/trestle/form/fields/date_picker.rb +2 -2
  45. data/lib/trestle/form/fields/form_group.rb +1 -1
  46. data/lib/trestle/resource.rb +38 -3
  47. data/lib/trestle/resource/builder.rb +10 -0
  48. data/lib/trestle/resource/controller.rb +36 -22
  49. data/lib/trestle/tab.rb +4 -0
  50. data/lib/trestle/table/column.rb +4 -4
  51. data/lib/trestle/table/row.rb +1 -1
  52. data/lib/trestle/version.rb +1 -1
  53. data/vendor/assets/javascripts/trestle/flatpickr.js.erb +2 -0
  54. metadata +12 -4
  55. data/app/views/trestle/application/_flash.html.erb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9c0345d2065a71ba7b11d5044f6cf0db55e08d8a9e0d5d3a83c715bf029f76f
4
- data.tar.gz: 8ce2f2a003d051204d1ad0017be70a9fe220ed0e4bf47bc2f3e6234ad751c316
3
+ metadata.gz: 53abdb7410eddcbc8964772c60fb759385fd0cc94574234473d29613e3dc62ef
4
+ data.tar.gz: a204be909209b9d159d1c6939911ccd7b5cddaf8437d7e20158c68c2051e3528
5
5
  SHA512:
6
- metadata.gz: 8638b5a1c9cd255fde7251ebb44d86f602523d5c6ef383044a033e2239973c6239ff4f7d018d9273fc232805b41bb7a4ad15f83110fd5e95d0cddcdf4c2fbca4
7
- data.tar.gz: b4f2bcdb3835d26b81186b790952c602069e4bae7a59cf0791370c455a6488502943f426fc91505595761a6fc1f6b57718bda02a0fff638fdd154c2b7c8b0d7d
6
+ metadata.gz: 7542b845d75753fd65f9908674a9728977f11657babfdf4a59489f5c023778ae29ce0a38d423d6fff4dc2249cb264cbe541b5e530ca5d9f60bfe0d06b901c1f6
7
+ data.tar.gz: 4a16e94cc8a64ad8691f99243b137ea897258ac4fa2595d7ce45573e1cc2ffd96dcba6e801c2e587f910587e89713fe671fa62209f79cd6c3f1cda8f6964ce65
data/.travis.yml CHANGED
@@ -10,4 +10,5 @@ gemfile:
10
10
  - gemfiles/rails-4.2.gemfile
11
11
  - gemfiles/rails-5.0.gemfile
12
12
  - gemfiles/rails-5.1.gemfile
13
+ - gemfiles/rails-5.2.gemfile
13
14
  - gemfiles/rails-edge.gemfile
@@ -1,15 +1,29 @@
1
+ Trestle.setupDatePicker = function(selectedDates, dateStr, instance) {
2
+ if ($(instance.input).data('allow-clear')) {
3
+ $('<a href="#">')
4
+ .on('click', function(e) {
5
+ e.preventDefault();
6
+ instance.clear();
7
+ })
8
+ .addClass('clear-datepicker')
9
+ .insertBefore(instance.altInput);
10
+ }
11
+ };
12
+
1
13
  Trestle.init(function(e, root) {
2
14
  $(root).find('input[type="date"][data-picker="true"]').flatpickr({
3
15
  allowInput: true,
4
16
  altInput: true,
5
- altFormat: "m/d/Y",
17
+ altFormat: Trestle.i18n["admin.datepicker.formats.date"] || "m/d/Y",
18
+ onReady: Trestle.setupDatePicker
6
19
  });
7
20
 
8
21
  $(root).find('input[type="datetime"][data-picker="true"], input[type="datetime-local"][data-picker="true"]').flatpickr({
9
22
  enableTime: true,
10
23
  allowInput: true,
11
24
  altInput: true,
12
- altFormat: "m/d/Y h:i K",
25
+ altFormat: Trestle.i18n["admin.datepicker.formats.datetime"] || "m/d/Y h:i K",
26
+ onReady: Trestle.setupDatePicker
13
27
  });
14
28
 
15
29
  $(root).find('input[type="time"][data-picker="true"]').flatpickr({
@@ -17,6 +31,7 @@ Trestle.init(function(e, root) {
17
31
  noCalendar: true,
18
32
  allowInput: true,
19
33
  altInput: true,
20
- altFormat: "h:i K"
34
+ altFormat: Trestle.i18n["admin.datepicker.formats.time"] || "h:i K",
35
+ onReady: Trestle.setupDatePicker
21
36
  });
22
37
  });
@@ -46,7 +46,7 @@ Trestle.Dialog.showError = function(title, errorText) {
46
46
  return dialog;
47
47
  };
48
48
 
49
- Trestle.Dialog.prototype.load = function(url) {
49
+ Trestle.Dialog.prototype.load = function(url, callback) {
50
50
  var dialog = this;
51
51
 
52
52
  dialog.show();
@@ -63,6 +63,10 @@ Trestle.Dialog.prototype.load = function(url) {
63
63
  },
64
64
  success: function(content) {
65
65
  dialog.setContent(content);
66
+
67
+ if (callback) {
68
+ callback.apply(dialog);
69
+ }
66
70
  },
67
71
  error: function(xhr, status, error) {
68
72
  var errorMessage = Trestle.i18n['trestle.dialog.error'] || 'The request could not be completed.';
@@ -63,7 +63,9 @@ Trestle.init(function(e, root) {
63
63
 
64
64
  // Delay to ensure form is still submitted
65
65
  setTimeout(function() {
66
- button.prop('disabled', true).addClass('loading');
66
+ if (form[0].checkValidity()) {
67
+ button.prop('disabled', true).addClass('loading');
68
+ }
67
69
  }, 1);
68
70
  });
69
71
  });
@@ -0,0 +1,23 @@
1
+ (function() {
2
+
3
+ // Some of Flatpickr's locale names differ from Rails. This maps the Rails I18n locale to their Flatpickr equivalent.
4
+ var FlatpickrLocaleConversions = { ca: "cat", el: "gr", nb: "no", vi: "vn" };
5
+
6
+ // Sets up localization for Trestle and its dependencies, in particular Flatpickr.
7
+ // This method accepts a list of locales in descending order of priority.
8
+ //
9
+ // Trestle.localize('es-MX', 'es', 'en');
10
+ //
11
+ Trestle.localize = function() {
12
+ for (var i = 0; i < arguments.length; ++i) {
13
+ var locale = arguments[i];
14
+ var flatpickrLocale = FlatpickrLocaleConversions[locale] || locale;
15
+
16
+ if (flatpickr.l10ns[flatpickrLocale]) {
17
+ flatpickr.localize(flatpickr.l10ns[flatpickrLocale]);
18
+ break;
19
+ }
20
+ }
21
+ };
22
+
23
+ })();
@@ -1,5 +1,6 @@
1
1
  .avatar {
2
2
  display: inline-block;
3
+ position: relative;
3
4
 
4
5
  width: 40px;
5
6
  height: 40px;
@@ -14,5 +15,27 @@
14
15
  height: 100%;
15
16
  object-fit: cover;
16
17
  border-radius: 50%;
18
+
19
+ position: relative;
20
+ z-index: 1;
21
+ }
22
+
23
+ .avatar-fallback {
24
+ position: absolute;
25
+ top: 0;
26
+ bottom: 0;
27
+ left: 0;
28
+ right: 0;
29
+ border-radius: 50%;
30
+
31
+ background: #ccc;
32
+ color: white;
33
+
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+
38
+ font-size: 11px;
39
+ font-weight: 500;
17
40
  }
18
41
  }
@@ -1,3 +1,31 @@
1
+ .clear-datepicker {
2
+ position: absolute;
3
+ top: 0;
4
+ right: 0;
5
+ z-index: 4;
6
+
7
+ width: 34px;
8
+ height: 34px;
9
+ line-height: 34px;
10
+ text-align: center;
11
+
12
+ color: #ccc;
13
+
14
+ &:hover, &:focus {
15
+ color: #aaa;
16
+ }
17
+
18
+ &::before {
19
+ @extend .fa;
20
+ content: $fa-var-times;
21
+ }
22
+
23
+ input[value=""] ~ &,
24
+ input:not([value]) ~ & {
25
+ display: none;
26
+ }
27
+ }
28
+
1
29
  .flatpickr-calendar {
2
30
  background: $theme-bg;
3
31
 
@@ -2,6 +2,24 @@
2
2
 
3
3
  .input-group {
4
4
  display: flex;
5
+
6
+ .field_with_errors {
7
+ flex: 1;
8
+
9
+ .form-control {
10
+ border-radius: $input-border-radius;
11
+ }
12
+
13
+ &:not(:last-child) .form-control {
14
+ border-top-right-radius: 0;
15
+ border-bottom-right-radius: 0;
16
+ }
17
+
18
+ &:not(:first-child) .form-control {
19
+ border-top-left-radius: 0;
20
+ border-bottom-left-radius: 0;
21
+ }
22
+ }
5
23
  }
6
24
 
7
25
  .input-group-addon {
@@ -1,10 +1,15 @@
1
1
  module Trestle
2
2
  module AvatarHelper
3
- def avatar(&block)
4
- content_tag(:div, class: "avatar", &block)
3
+ def avatar(options={})
4
+ content_tag(:div, class: "avatar") do
5
+ concat content_tag(:span, options[:fallback], class: "avatar-fallback") if options[:fallback]
6
+ concat yield if block_given?
7
+ end
5
8
  end
6
9
 
7
10
  def gravatar(email, options={})
11
+ options = { d: "mm" }.merge(options)
12
+
8
13
  url = "https://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email.to_s.downcase)}.png"
9
14
  url << "?#{options.to_query}" if options.any?
10
15
 
@@ -5,7 +5,7 @@ module Trestle
5
5
  end
6
6
 
7
7
  def instance_has_errors?
8
- @instance && @instance.respond_to?(:errors) && @instance.errors.any?
8
+ instance.errors.any? rescue false
9
9
  end
10
10
  end
11
11
  end
@@ -2,7 +2,7 @@ module Trestle
2
2
  module FormHelper
3
3
  def trestle_form_for(instance, options={}, &block)
4
4
  options[:builder] ||= Form::Builder
5
- options[:as] ||= admin.admin_name.singularize
5
+ options[:as] ||= admin.parameter_name
6
6
 
7
7
  options[:data] ||= {}
8
8
  options[:data].merge!(remote: true, type: :html, behavior: "trestle-form", turbolinks: false)
@@ -0,0 +1,14 @@
1
+ module Trestle
2
+ module I18nHelper
3
+ def i18n_fallbacks(locale=I18n.locale)
4
+ if I18n.respond_to?(:fallbacks)
5
+ I18n.fallbacks[locale]
6
+ elsif locale.to_s.include?("-")
7
+ fallback = locale.to_s.split("-").first
8
+ [locale, fallback]
9
+ else
10
+ [locale]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -5,9 +5,9 @@ module Trestle
5
5
  end
6
6
 
7
7
  def tab(name, options={})
8
- tabs[name] = Tab.new(name, options)
8
+ tabs[name] = tab = Tab.new(name, options)
9
9
 
10
- content_tag(:div, id: "tab-#{name}", class: ["tab-pane", ('active' if name == tabs.keys.first)], role: "tabpanel") do
10
+ content_tag(:div, id: tab.id(("modal" if dialog_request?)), class: ["tab-pane", ('active' if name == tabs.keys.first)], role: "tabpanel") do
11
11
  if block_given?
12
12
  yield
13
13
  elsif options[:partial]
@@ -3,38 +3,51 @@ module Trestle
3
3
  DIALOG_ACTIONS = [:new, :show, :edit]
4
4
 
5
5
  def admin_link_to(content, instance_or_url=nil, options={}, &block)
6
+ # Block given - ignore content parameter and capture content from block
6
7
  if block_given?
7
8
  instance_or_url, options = content, instance_or_url || {}
8
9
  content = capture(&block)
9
10
  end
10
11
 
11
12
  if instance_or_url.is_a?(String)
13
+ # Treat string URL as regular link
12
14
  link_to(content, instance_or_url, options)
13
15
  else
16
+ # Normalize options if instance is not provided
14
17
  if instance_or_url.is_a?(Hash)
15
- instance_or_url, options = nil, instance_or_url
18
+ instance, options = nil, instance_or_url
19
+ else
20
+ instance = instance_or_url
16
21
  end
17
22
 
23
+ # Determine admin
18
24
  if options.key?(:admin)
19
25
  admin = Trestle.lookup(options.delete(:admin))
20
- elsif instance_or_url.respond_to?(:id)
21
- admin = admin_for(instance_or_url)
26
+ elsif instance
27
+ admin = admin_for(instance)
22
28
  end
23
29
 
24
30
  admin ||= self.admin if respond_to?(:admin)
25
31
 
26
32
  if admin
33
+ # Generate path
27
34
  action = options.delete(:action) || :show
28
-
29
35
  params = options.delete(:params) || {}
30
- params[:id] ||= admin.to_param(instance_or_url) if instance_or_url
31
36
 
37
+ if admin.respond_to?(:instance_path) && instance
38
+ path = admin.instance_path(instance, params.reverse_merge(action: action))
39
+ else
40
+ params[:id] ||= admin.to_param(instance) if instance
41
+ path = admin.path(action, params)
42
+ end
43
+
44
+ # Determine link data options
32
45
  if DIALOG_ACTIONS.include?(action) && admin.form.dialog?
33
46
  options[:data] ||= {}
34
47
  options[:data][:behavior] ||= "dialog"
35
48
  end
36
49
 
37
- link_to(content, admin.path(action, params), options)
50
+ link_to(content, path, options)
38
51
  else
39
52
  raise ActionController::UrlGenerationError, "An admin could not be inferred. Please specify an admin using the :admin option."
40
53
  end
@@ -42,14 +55,29 @@ module Trestle
42
55
  end
43
56
 
44
57
  def admin_url_for(instance, options={})
45
- admin = Trestle.lookup(options[:admin]) if options.key?(:admin)
58
+ admin = Trestle.lookup(options.delete(:admin)) if options.key?(:admin)
46
59
  admin ||= admin_for(instance)
60
+ return unless admin
47
61
 
48
- admin.path(options[:action] || :show, id: admin.to_param(instance)) if admin
62
+ if admin.respond_to?(:instance_path)
63
+ admin.instance_path(instance, options)
64
+ else
65
+ admin.path(options[:action] || :show, id: admin.to_param(instance))
66
+ end
49
67
  end
50
68
 
51
69
  def admin_for(instance)
52
- Trestle.admins[instance.class.name.underscore.pluralize]
70
+ klass = instance.class
71
+
72
+ while klass
73
+ admin = Trestle.admins[klass.name.underscore.pluralize]
74
+ return admin if admin
75
+
76
+ klass = klass.superclass
77
+ end
78
+
79
+ # No admin found
80
+ nil
53
81
  end
54
82
  end
55
83
  end
@@ -26,9 +26,7 @@
26
26
  Trestle.i18n['<%= key %>'] = "<%= escape_javascript(t(key, default: t(key, locale: :en))) %>";
27
27
  <% end %>
28
28
 
29
- if (flatpickr.l10ns['<%= I18n.locale %>']) {
30
- flatpickr.localize(flatpickr.l10ns['<%= I18n.locale %>']);
31
- }
29
+ Trestle.localize(<%= i18n_fallbacks.map { |l| "'#{l}'" }.join(", ").html_safe %>);
32
30
  </script>
33
31
 
34
32
  <%= hook :javascripts %>
@@ -1,7 +1,7 @@
1
1
  <%= render "header", hide_breadcrumbs: local_assigns.fetch(:hide_breadcrumbs, false) if local_assigns.fetch(:header, true) %>
2
2
 
3
3
  <div class="main-content-area">
4
- <%= render "flash" %>
4
+ <%= render "trestle/flash/flash" %>
5
5
  <%= render "utilities" %>
6
6
  <%= render "tabs" %>
7
7
 
@@ -2,7 +2,7 @@
2
2
  <ul class="nav nav-tabs">
3
3
  <% tabs.each do |name, tab| %>
4
4
  <li<% if name == tabs.keys.first %> class="active"<% end %>>
5
- <%= link_to tab.label, "#tab-#{name}", role: "tab", data: { toggle: "tab" } %>
5
+ <%= link_to tab.label, "##{tab.id(("modal" if dialog_request?))}", role: "tab", data: { toggle: "tab" } %>
6
6
  </li>
7
7
  <% end %>
8
8
 
@@ -4,8 +4,8 @@
4
4
  <%= icon %>
5
5
 
6
6
  <div class="alert-content">
7
- <h3><%= title %></h3>
8
- <p><%= message %></p>
7
+ <h3><%= alert[:title] %></h3>
8
+ <p><%= alert[:message] %></p>
9
9
 
10
10
  <%= yield if block_given? %>
11
11
  </div>
@@ -0,0 +1,8 @@
1
+ <%= link_to "Debug errors", "#debug-errors", class: "toggle-debug-errors small", data: { toggle: "collapse" } %>
2
+ <div id="debug-errors" class="debug-errors collapse">
3
+ <ul>
4
+ <% instance.errors.each do |key, message| %>
5
+ <li class="small"><tt><%= key %>:</tt> <%= message %></li>
6
+ <% end %>
7
+ </ul>
8
+ </div>
@@ -0,0 +1,7 @@
1
+ <% if flash[:message] -%>
2
+ <%= render "trestle/flash/alert", html_class: "alert-success", icon: icon("alert-icon ion-ios-checkmark-outline"), alert: flash[:message].with_indifferent_access %>
3
+ <% elsif flash[:error] -%>
4
+ <%= render layout: "trestle/flash/alert", locals: { html_class: "alert-danger", icon: icon("alert-icon ion-ios-close-outline"), alert: flash[:error].with_indifferent_access } do %>
5
+ <%= render "trestle/flash/debug" if debug_form_errors? %>
6
+ <% end %>
7
+ <% end -%>
@@ -1,18 +1,18 @@
1
- <% title = t("admin.titles.edit", default: "Editing %{model_name}", model_name: admin.model_name.titleize, pluralized_model_name: admin.model_name.plural.titleize) %>
1
+ <% title = admin.t("titles.edit", default: "Editing %{model_name}") %>
2
2
 
3
3
  <% content_for(:title, title) %>
4
- <% breadcrumb title %>
4
+ <% breadcrumb(title) %>
5
5
 
6
6
  <% content_for(:primary_toolbar) do %>
7
- <%= button_tag t("admin.buttons.save", default: "Save %{model_name}", model_name: admin.model_name), class: "btn btn-success btn-lg" if admin.actions.include?(:update) %>
7
+ <%= button_tag admin.t("buttons.save", default: "Save %{model_name}"), class: "btn btn-success btn-lg" if admin.actions.include?(:update) %>
8
8
  <% end %>
9
9
 
10
10
  <% content_for(:secondary_toolbar) do %>
11
11
  <%= admin_link_to instance, action: :destroy, method: :delete, class: "btn btn-danger", data: { toggle: "confirm-delete", placement: "bottom" } do %>
12
- <%= icon("fa fa-trash") %> <%= t("admin.buttons.delete", default: "Delete %{model_name}", model_name: admin.model_name) %>
12
+ <%= icon("fa fa-trash") %> <%= admin.t("buttons.delete", default: "Delete %{model_name}") %>
13
13
  <% end if admin.actions.include?(:destroy) %>
14
14
  <% end %>
15
15
 
16
- <%= trestle_form_for instance, url: admin.actions.include?(:update) ? admin.path(:update, id: admin.to_param(instance)) : "#", method: :patch do |f| %>
16
+ <%= trestle_form_for instance, url: admin.actions.include?(:update) ? admin.instance_path(instance, action: :update) : "#", method: :patch do |f| %>
17
17
  <%= render partial: "form", layout: dialog_request? ? "dialog" : "layout" %>
18
18
  <% end %>