trestle 0.8.11 → 0.8.12

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +11 -1
  3. data/README.md +8 -3
  4. data/app/assets/javascripts/trestle/components/_confirmation.js +22 -22
  5. data/app/assets/stylesheets/trestle/components/_dropdown.scss +28 -0
  6. data/app/assets/stylesheets/trestle/components/_fields.scss +5 -0
  7. data/app/assets/stylesheets/trestle/core/_defaults.scss +2 -0
  8. data/app/controllers/trestle/dashboard_controller.rb +1 -1
  9. data/app/helpers/trestle/i18n_helper.rb +6 -0
  10. data/app/helpers/trestle/panel_helper.rb +1 -1
  11. data/app/helpers/trestle/toolbars_helper.rb +2 -1
  12. data/app/views/layouts/trestle/admin.html.erb +5 -3
  13. data/app/views/trestle/application/_dialog.html.erb +2 -2
  14. data/app/views/trestle/application/_header.html.erb +2 -2
  15. data/app/views/trestle/shared/_sidebar.html.erb +1 -1
  16. data/config/locales/en.yml +1 -0
  17. data/config/locales/ko.rb +18 -0
  18. data/config/locales/ko.yml +96 -0
  19. data/gemfiles/rails-4.2.gemfile +1 -1
  20. data/gemfiles/rails-5.0.gemfile +1 -1
  21. data/gemfiles/rails-5.1.gemfile +1 -1
  22. data/gemfiles/rails-5.2.gemfile +1 -1
  23. data/gemfiles/rails-edge.gemfile +1 -1
  24. data/lib/generators/trestle/install/templates/trestle.rb.erb +4 -0
  25. data/lib/trestle.rb +5 -3
  26. data/lib/trestle/adapters/adapter.rb +2 -17
  27. data/lib/trestle/configuration.rb +3 -0
  28. data/lib/trestle/evaluation_context.rb +24 -0
  29. data/lib/trestle/form/fields/password_field.rb +4 -0
  30. data/lib/trestle/form/fields/select.rb +3 -1
  31. data/lib/trestle/form/renderer.rb +1 -0
  32. data/lib/trestle/navigation.rb +6 -6
  33. data/lib/trestle/navigation/block.rb +6 -6
  34. data/lib/trestle/resource.rb +6 -1
  35. data/lib/trestle/resource/builder.rb +6 -5
  36. data/lib/trestle/scopes.rb +23 -0
  37. data/lib/trestle/scopes/block.rb +38 -0
  38. data/lib/trestle/scopes/scope.rb +49 -0
  39. data/lib/trestle/toolbar.rb +11 -0
  40. data/lib/trestle/toolbar/builder.rb +8 -27
  41. data/lib/trestle/toolbar/item.rb +118 -0
  42. data/lib/trestle/toolbar/menu.rb +63 -0
  43. data/lib/trestle/version.rb +1 -1
  44. data/trestle.gemspec +4 -4
  45. metadata +34 -28
  46. data/lib/trestle/scope.rb +0 -47
@@ -15,6 +15,10 @@ Trestle.configure do |config|
15
15
  #
16
16
  # config.site_logo_small = "logo-small.png"
17
17
 
18
+ # Speficy a favicon to be used within the admin.
19
+ #
20
+ # config.favicon = "favicon.ico"
21
+
18
22
  # Set the text shown in the page footer within the admin.
19
23
  # Defaults to 'Powered by Trestle'.
20
24
  #
@@ -17,6 +17,7 @@ module Trestle
17
17
  autoload :Configurable
18
18
  autoload :Configuration
19
19
  autoload :Display
20
+ autoload :EvaluationContext
20
21
  autoload :Form
21
22
  autoload :Hook
22
23
  autoload :ModelName
@@ -24,7 +25,7 @@ module Trestle
24
25
  autoload :Options
25
26
  autoload :Reloader
26
27
  autoload :Resource
27
- autoload :Scope
28
+ autoload :Scopes
28
29
  autoload :Tab
29
30
  autoload :Table
30
31
  autoload :Toolbar
@@ -57,8 +58,9 @@ module Trestle
57
58
  config.configure(&block)
58
59
  end
59
60
 
60
- def self.navigation
61
- Navigation.build(config.menus + admins.values.map(&:menu).compact)
61
+ def self.navigation(context)
62
+ blocks = config.menus + admins.values.map(&:menu).compact
63
+ Navigation.build(blocks, context)
62
64
  end
63
65
  end
64
66
 
@@ -1,6 +1,8 @@
1
1
  module Trestle
2
2
  module Adapters
3
3
  class Adapter
4
+ include EvaluationContext
5
+
4
6
  attr_reader :admin
5
7
  delegate :model, to: :admin
6
8
 
@@ -179,23 +181,6 @@ module Trestle
179
181
  def default_form_attributes
180
182
  raise NotImplementedError
181
183
  end
182
-
183
- protected
184
- # Missing methods are called on the given context if available.
185
- #
186
- # We include private methods as methods such as current_user
187
- # are usually declared as private or protected.
188
- def method_missing(name, *args, &block)
189
- if @context && @context.respond_to?(name, true)
190
- @context.send(name, *args, &block)
191
- else
192
- super
193
- end
194
- end
195
-
196
- def respond_to_missing?(name, include_private=false)
197
- (@context && @context.respond_to?(name, true)) || super
198
- end
199
184
  end
200
185
  end
201
186
  end
@@ -13,6 +13,9 @@ module Trestle
13
13
  # Custom image for the collapsed/tablet navigation
14
14
  option :site_logo_small
15
15
 
16
+ # Favicon used within the admin
17
+ option :favicon
18
+
16
19
  # Text shown in the admin page footer
17
20
  option :footer, -> { I18n.t("trestle.footer", default: "Powered by Trestle") }
18
21
 
@@ -0,0 +1,24 @@
1
+ module Trestle
2
+ # This module facilitiates the delegation of missing methods to a given @context variable.
3
+ #
4
+ # This allows code such as adapter and navigation blocks to be evaluated with access to methods from
5
+ # both the Adapter/Navigation instance, as well as the controller/view from where they are invoked.
6
+ module EvaluationContext
7
+ protected
8
+ # Missing methods are called on the given context if available.
9
+ #
10
+ # We include private methods as methods such as current_user
11
+ # are usually declared as private or protected.
12
+ def method_missing(name, *args, &block)
13
+ if @context && @context.respond_to?(name, true)
14
+ @context.send(name, *args, &block)
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def respond_to_missing?(name, include_private=false)
21
+ (@context && @context.respond_to?(name, true)) || super
22
+ end
23
+ end
24
+ end
@@ -2,6 +2,10 @@ class Trestle::Form::Fields::PasswordField < Trestle::Form::Fields::FormControl
2
2
  def field
3
3
  builder.raw_password_field(name, options)
4
4
  end
5
+
6
+ def defaults
7
+ super.merge(autocomplete: "new-password")
8
+ end
5
9
  end
6
10
 
7
11
  Trestle::Form::Builder.register(:password_field, Trestle::Form::Fields::PasswordField)
@@ -7,7 +7,9 @@ module Trestle
7
7
  def initialize(builder, template, name, choices=nil, options={}, html_options={}, &block)
8
8
  super(builder, template, name, options, &block)
9
9
 
10
- @choices = Choices.new(choices || default_choices)
10
+ @choices = choices || default_choices
11
+ @choices = Choices.new(@choices) if @choices.nil? || @choices.is_a?(Enumerable)
12
+
11
13
  @html_options = default_html_options.merge(html_options)
12
14
  end
13
15
 
@@ -3,6 +3,7 @@ module Trestle
3
3
  class Renderer
4
4
  include ::ActionView::Context
5
5
  include ::ActionView::Helpers::CaptureHelper
6
+ include HookHelper
6
7
 
7
8
  # Whitelisted helpers will concatenate their result to the output buffer when called.
8
9
  WHITELISTED_HELPERS = [:row, :col, :render, :tab, :table, :divider, :h1, :h2, :h3, :h4, :h5, :h6, :panel, :well]
@@ -29,12 +29,12 @@ module Trestle
29
29
  sorted.first.first if sorted.any?
30
30
  end
31
31
 
32
- def visible(context)
33
- self.class.new(items.select { |item| item.visible?(context) })
34
- end
35
-
36
- def self.build(blocks)
37
- new(blocks.map(&:items).flatten)
32
+ def self.build(blocks, context)
33
+ new(blocks.map { |block|
34
+ block.items(context)
35
+ }.flatten.select { |item|
36
+ item.visible?(context)
37
+ })
38
38
  end
39
39
 
40
40
  private
@@ -8,19 +8,19 @@ module Trestle
8
8
  @block = block
9
9
  end
10
10
 
11
- def items
12
- context = Context.new(@admin)
11
+ def items(context)
12
+ context = Evaluator.new(@admin, context)
13
13
  context.instance_exec(@admin, &block)
14
14
  context.items
15
15
  end
16
16
 
17
- class Context
18
- include Rails.application.routes.url_helpers if Rails.application
17
+ class Evaluator
18
+ include EvaluationContext
19
19
 
20
20
  attr_reader :items
21
21
 
22
- def initialize(admin=nil)
23
- @admin = admin
22
+ def initialize(admin=nil, context=nil)
23
+ @admin, @context = admin, context
24
24
  @items = []
25
25
  end
26
26
 
@@ -50,6 +50,11 @@ module Trestle
50
50
  Collection.new(self, options).prepare(params)
51
51
  end
52
52
 
53
+ # Evaluates the admin's scope block(s) and returns a hash of Scope objects keyed by the scope name
54
+ def scopes
55
+ @scopes ||= self.class.scopes.evaluate(@context)
56
+ end
57
+
53
58
  class << self
54
59
  # Deprecated: use instance method instead
55
60
  def prepare_collection(params, options={})
@@ -57,7 +62,7 @@ module Trestle
57
62
  end
58
63
 
59
64
  def scopes
60
- @scopes ||= {}
65
+ @scopes ||= Scopes.new(self)
61
66
  end
62
67
 
63
68
  def column_sorts
@@ -83,13 +83,14 @@ module Trestle
83
83
  admin.define_adapter_method(:count, &block)
84
84
  end
85
85
 
86
+ def scopes(&block)
87
+ admin.scopes.append(&block)
88
+ end
89
+
86
90
  def scope(name, scope=nil, options={}, &block)
87
- if scope.is_a?(Hash)
88
- options = scope
89
- scope = nil
91
+ scopes do
92
+ scope(name, scope, options, &block)
90
93
  end
91
-
92
- admin.scopes[name] = Scope.new(admin, name, options, &(scope || block))
93
94
  end
94
95
 
95
96
  def return_to(options={}, &block)
@@ -0,0 +1,23 @@
1
+ module Trestle
2
+ class Scopes
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Block
6
+ autoload :Scope
7
+
8
+ attr_reader :admin, :blocks
9
+
10
+ def initialize(admin)
11
+ @admin = admin
12
+ @blocks = []
13
+ end
14
+
15
+ def append(&block)
16
+ @blocks << Block.new(admin, &block)
17
+ end
18
+
19
+ def evaluate(context)
20
+ @blocks.map { |block| block.scopes(context) }.flatten.index_by(&:name)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ module Trestle
2
+ class Scopes
3
+ class Block
4
+ attr_reader :block, :admin
5
+
6
+ def initialize(admin=nil, &block)
7
+ @admin = admin
8
+ @block = block
9
+ end
10
+
11
+ def scopes(context)
12
+ context = Evaluator.new(@admin, context)
13
+ context.instance_exec(@admin, &block)
14
+ context.scopes
15
+ end
16
+
17
+ class Evaluator
18
+ include EvaluationContext
19
+
20
+ attr_reader :scopes
21
+
22
+ def initialize(admin, context=nil)
23
+ @admin, @context = admin, context
24
+ @scopes = []
25
+ end
26
+
27
+ def scope(name, scope=nil, options={}, &block)
28
+ if scope.is_a?(Hash)
29
+ options = scope
30
+ scope = nil
31
+ end
32
+
33
+ scopes << Scope.new(@admin, name, options, &(scope || block))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,49 @@
1
+ module Trestle
2
+ class Scopes
3
+ class Scope
4
+ attr_reader :name, :options, :block
5
+
6
+ def initialize(admin, name, options={}, &block)
7
+ @admin, @name, @options, @block = admin, name, options, block
8
+ end
9
+
10
+ def to_param
11
+ name
12
+ end
13
+
14
+ def label
15
+ @options[:label] || I18n.t("admin.scopes.#{name}", default: name.to_s.humanize.titleize)
16
+ end
17
+
18
+ def default?
19
+ @options[:default] == true
20
+ end
21
+
22
+ def apply(collection)
23
+ if @block
24
+ if @block.arity == 1
25
+ @admin.instance_exec(collection, &@block)
26
+ else
27
+ @admin.instance_exec(&@block)
28
+ end
29
+ else
30
+ collection.public_send(name)
31
+ end
32
+ end
33
+
34
+ def count(collection)
35
+ @admin.count(@admin.merge_scopes(collection, apply(collection)))
36
+ end
37
+
38
+ def active?(params)
39
+ active_scopes = Array(params[:scope])
40
+
41
+ if active_scopes.any?
42
+ active_scopes.include?(to_param.to_s)
43
+ else
44
+ default?
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -4,9 +4,20 @@ module Trestle
4
4
 
5
5
  autoload :Builder
6
6
  autoload :Context
7
+ autoload :Menu
8
+
9
+ autoload_at "trestle/toolbar/item" do
10
+ autoload :Button
11
+ autoload :Dropdown
12
+ autoload :Link
13
+ end
7
14
 
8
15
  def initialize(builder=Builder)
9
16
  @builder = builder
17
+ clear!
18
+ end
19
+
20
+ def clear!
10
21
  @blocks = []
11
22
  end
12
23
 
@@ -5,17 +5,16 @@ module Trestle
5
5
  @template = template
6
6
  end
7
7
 
8
- def button(content, options={})
9
- options[:class] = button_classes_from_options(options)
10
-
11
- button_tag(button_label(content, options), options)
8
+ def button(label, options={}, &block)
9
+ Button.new(@template, label, options, &block)
12
10
  end
13
11
 
14
- def link(content, instance_or_url={}, options={})
15
- options = instance_or_url if instance_or_url.is_a?(Hash)
16
- options[:class] = button_classes_from_options(options)
12
+ def link(label, instance_or_url={}, options={}, &block)
13
+ Link.new(@template, label, instance_or_url, options, &block)
14
+ end
17
15
 
18
- admin_link_to(button_label(content, options), instance_or_url, options)
16
+ def dropdown(label=nil, options={}, &block)
17
+ Dropdown.new(@template, label, options, &block)
19
18
  end
20
19
 
21
20
  # Only methods explicitly tagged as builder methods will be automatically
@@ -28,25 +27,7 @@ module Trestle
28
27
  self.builder_methods += methods
29
28
  end
30
29
 
31
- builder_method :button, :link
32
-
33
- private
34
- delegate :admin_link_to, :button_tag, :content_tag, :safe_join, :icon, to: :@template
35
-
36
- def button_classes_from_options(options)
37
- classes = (options[:class] || "").split("\s")
38
- classes.unshift("btn-#{options.delete(:style) || "default"}")
39
- classes.unshift("btn") unless classes.include?("btn")
40
- classes.push("has-icon") if options[:icon]
41
- classes
42
- end
43
-
44
- def button_label(content, options)
45
- icon = icon(options.delete(:icon)) if options[:icon]
46
- label = content_tag(:span, content, class: "btn-label")
47
-
48
- safe_join([icon, label].compact, " ")
49
- end
30
+ builder_method :button, :link, :dropdown
50
31
  end
51
32
  end
52
33
  end
@@ -0,0 +1,118 @@
1
+ module Trestle
2
+ class Toolbar
3
+ class Item
4
+ attr_reader :label, :menu
5
+
6
+ delegate :admin_link_to, :button_tag, :content_tag, :safe_join, :icon, to: :@template
7
+
8
+ def initialize(template, label, options={}, &block)
9
+ @template = template
10
+ @label, @options, @block = label, options
11
+
12
+ @menu = Menu.new(template)
13
+ @menu.build(&block) if block_given?
14
+
15
+ @icon = options.delete(:icon)
16
+ @style = options.delete(:style)
17
+ end
18
+
19
+ def ==(other)
20
+ to_s == other.to_s
21
+ end
22
+
23
+ def to_s
24
+ if menu.items.any?
25
+ content_tag(:div, class: "btn-group", role: "group") do
26
+ safe_join([render, render_menu], "\n")
27
+ end
28
+ else
29
+ render
30
+ end
31
+ end
32
+
33
+ def render
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def render_menu
38
+ [
39
+ menu.render_toggle(class: button_style_classes),
40
+ menu.render_items
41
+ ]
42
+ end
43
+
44
+ def options
45
+ @options.merge(class: button_classes)
46
+ end
47
+
48
+ def button_classes
49
+ classes = (@options[:class] || "").split(/\s/)
50
+ classes.push(*button_style_classes)
51
+ classes.push("has-icon") if @icon
52
+ classes.uniq
53
+ end
54
+
55
+ def button_label(content, options)
56
+ icon = icon(@icon) if @icon
57
+ label = content_tag(:span, content, class: "btn-label")
58
+
59
+ safe_join([icon, label].compact, " ")
60
+ end
61
+
62
+ def button_style
63
+ @style || "default"
64
+ end
65
+
66
+ def button_style_classes
67
+ ["btn", "btn-#{button_style}"]
68
+ end
69
+ end
70
+
71
+ class Button < Item
72
+ def render
73
+ button_tag(button_label(label, options), options)
74
+ end
75
+ end
76
+
77
+ class Link < Item
78
+ attr_reader :instance_or_url
79
+
80
+ def initialize(template, label, instance_or_url={}, options={}, &block)
81
+ if instance_or_url.is_a?(Hash)
82
+ super(template, label, instance_or_url, &block)
83
+ else
84
+ super(template, label, options, &block)
85
+ @instance_or_url = instance_or_url
86
+ end
87
+ end
88
+
89
+ def render
90
+ if @instance_or_url
91
+ admin_link_to(button_label(label, options), instance_or_url, options)
92
+ else
93
+ admin_link_to(button_label(label, options), options)
94
+ end
95
+ end
96
+ end
97
+
98
+ class Dropdown < Button
99
+ def options
100
+ super.merge(type: "button", data: { toggle: "dropdown" })
101
+ end
102
+
103
+ def label
104
+ safe_join([
105
+ super, content_tag(:span, "", class: "caret")
106
+ ], " ")
107
+ end
108
+
109
+ def button_style_classes
110
+ super + ["dropdown-toggle"]
111
+ end
112
+
113
+ def render_menu
114
+ [menu.render_items]
115
+ end
116
+ end
117
+ end
118
+ end