trestle 0.8.7 → 0.8.8

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 (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 %>