trestle 0.8.11 → 0.8.12

Sign up to get free protection for your applications and to get access to all the features.
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