trestle 0.8.11 → 0.8.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +11 -1
- data/README.md +8 -3
- data/app/assets/javascripts/trestle/components/_confirmation.js +22 -22
- data/app/assets/stylesheets/trestle/components/_dropdown.scss +28 -0
- data/app/assets/stylesheets/trestle/components/_fields.scss +5 -0
- data/app/assets/stylesheets/trestle/core/_defaults.scss +2 -0
- data/app/controllers/trestle/dashboard_controller.rb +1 -1
- data/app/helpers/trestle/i18n_helper.rb +6 -0
- data/app/helpers/trestle/panel_helper.rb +1 -1
- data/app/helpers/trestle/toolbars_helper.rb +2 -1
- data/app/views/layouts/trestle/admin.html.erb +5 -3
- data/app/views/trestle/application/_dialog.html.erb +2 -2
- data/app/views/trestle/application/_header.html.erb +2 -2
- data/app/views/trestle/shared/_sidebar.html.erb +1 -1
- data/config/locales/en.yml +1 -0
- data/config/locales/ko.rb +18 -0
- data/config/locales/ko.yml +96 -0
- data/gemfiles/rails-4.2.gemfile +1 -1
- data/gemfiles/rails-5.0.gemfile +1 -1
- data/gemfiles/rails-5.1.gemfile +1 -1
- data/gemfiles/rails-5.2.gemfile +1 -1
- data/gemfiles/rails-edge.gemfile +1 -1
- data/lib/generators/trestle/install/templates/trestle.rb.erb +4 -0
- data/lib/trestle.rb +5 -3
- data/lib/trestle/adapters/adapter.rb +2 -17
- data/lib/trestle/configuration.rb +3 -0
- data/lib/trestle/evaluation_context.rb +24 -0
- data/lib/trestle/form/fields/password_field.rb +4 -0
- data/lib/trestle/form/fields/select.rb +3 -1
- data/lib/trestle/form/renderer.rb +1 -0
- data/lib/trestle/navigation.rb +6 -6
- data/lib/trestle/navigation/block.rb +6 -6
- data/lib/trestle/resource.rb +6 -1
- data/lib/trestle/resource/builder.rb +6 -5
- data/lib/trestle/scopes.rb +23 -0
- data/lib/trestle/scopes/block.rb +38 -0
- data/lib/trestle/scopes/scope.rb +49 -0
- data/lib/trestle/toolbar.rb +11 -0
- data/lib/trestle/toolbar/builder.rb +8 -27
- data/lib/trestle/toolbar/item.rb +118 -0
- data/lib/trestle/toolbar/menu.rb +63 -0
- data/lib/trestle/version.rb +1 -1
- data/trestle.gemspec +4 -4
- metadata +34 -28
- 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
|
#
|
data/lib/trestle.rb
CHANGED
@@ -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 :
|
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
|
-
|
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 =
|
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]
|
data/lib/trestle/navigation.rb
CHANGED
@@ -29,12 +29,12 @@ module Trestle
|
|
29
29
|
sorted.first.first if sorted.any?
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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 =
|
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
|
18
|
-
include
|
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
|
|
data/lib/trestle/resource.rb
CHANGED
@@ -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
|
-
|
88
|
-
options
|
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
|
data/lib/trestle/toolbar.rb
CHANGED
@@ -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(
|
9
|
-
|
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(
|
15
|
-
|
16
|
-
|
12
|
+
def link(label, instance_or_url={}, options={}, &block)
|
13
|
+
Link.new(@template, label, instance_or_url, options, &block)
|
14
|
+
end
|
17
15
|
|
18
|
-
|
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
|