trestle 0.10.0 → 0.10.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +4 -0
- data/app/assets/bundle/trestle/admin.css +3 -3
- data/app/assets/bundle/trestle/admin.js +13 -13
- data/app/assets/bundle/trestle/photoswipe-2d522a3abaa59f8a8f73.digested.js +6 -0
- data/app/helpers/trestle/avatar_helper.rb +20 -14
- data/app/helpers/trestle/card_helper.rb +27 -9
- data/app/helpers/trestle/container_helper.rb +37 -6
- data/app/helpers/trestle/display_helper.rb +11 -0
- data/app/helpers/trestle/flash_helper.rb +1 -10
- data/app/helpers/trestle/form_helper.rb +32 -18
- data/app/helpers/trestle/format_helper.rb +47 -17
- data/app/helpers/trestle/gravatar_helper.rb +48 -0
- data/app/helpers/trestle/grid_helper.rb +12 -14
- data/app/helpers/trestle/headings_helper.rb +2 -23
- data/app/helpers/trestle/i18n_helper.rb +1 -0
- data/app/helpers/trestle/icon_helper.rb +16 -3
- data/app/helpers/trestle/layout_helper.rb +1 -0
- data/app/helpers/trestle/modal_helper.rb +21 -2
- data/app/helpers/trestle/navigation_helper.rb +1 -0
- data/app/helpers/trestle/pagination_helper.rb +1 -0
- data/app/helpers/trestle/params_helper.rb +32 -0
- data/app/helpers/trestle/sort_helper.rb +38 -7
- data/app/helpers/trestle/status_helper.rb +19 -3
- data/app/helpers/trestle/tab_helper.rb +42 -7
- data/app/helpers/trestle/table_helper.rb +23 -23
- data/app/helpers/trestle/timestamp_helper.rb +18 -25
- data/app/helpers/trestle/title_helper.rb +2 -0
- data/app/helpers/trestle/toolbars_helper.rb +2 -1
- data/app/helpers/trestle/turbo/frame_helper.rb +25 -14
- data/app/helpers/trestle/url_helper.rb +124 -54
- data/app/views/kaminari/trestle/_first_page.html.erb +1 -2
- data/app/views/kaminari/trestle/_gap.html.erb +0 -1
- data/app/views/kaminari/trestle/_last_page.html.erb +1 -2
- data/app/views/kaminari/trestle/_page.html.erb +1 -2
- data/app/views/kaminari/trestle/_paginator.html.erb +0 -1
- data/app/views/layouts/trestle/admin.html.erb +3 -3
- data/app/views/layouts/trestle/modal.html.erb +2 -2
- data/app/views/trestle/application/_layout.html.erb +22 -18
- data/app/views/trestle/application/_tabs.html.erb +1 -1
- data/app/views/trestle/flash/_alert.html.erb +3 -1
- data/app/views/trestle/flash/_flash.html.erb +7 -4
- data/app/views/trestle/resource/_scopes.html.erb +3 -3
- data/app/views/trestle/resource/create.turbo_stream.erb +1 -0
- data/app/views/trestle/resource/destroy.turbo_stream.erb +2 -0
- data/app/views/trestle/resource/index.html.erb +10 -12
- data/app/views/trestle/resource/update.turbo_stream.erb +1 -0
- data/app/views/trestle/shared/_sidebar.html.erb +8 -8
- data/app/views/trestle/table/_table.html.erb +2 -2
- data/config/locales/pt-BR.yml +13 -13
- data/frontend/css/components/_scopes.scss +6 -7
- data/frontend/css/core/_theme.scss +3 -3
- data/frontend/css/layout/_sidebar.scss +2 -0
- data/frontend/js/controllers/flatpickr_controller.js +2 -2
- data/frontend/js/controllers/lightbox_controller.js +3 -3
- data/frontend/js/controllers/modal_trigger_controller.js +2 -2
- data/frontend/js/controllers/sidebar_controller.js +13 -3
- data/frontend/js/controllers/tab_errors_controller.js +2 -2
- data/frontend/js/core/backdrop.js +30 -28
- data/frontend/js/core/error_modal.js +7 -9
- data/frontend/js/core/fetch.js +2 -0
- data/lib/trestle/admin.rb +9 -2
- data/lib/trestle/configuration.rb +3 -0
- data/lib/trestle/engine.rb +1 -1
- data/lib/trestle/evaluation_context.rb +2 -4
- data/lib/trestle/form/automatic.rb +1 -1
- data/lib/trestle/form/field.rb +1 -1
- data/lib/trestle/form/fields/check_box.rb +1 -1
- data/lib/trestle/form/fields/collection_check_boxes.rb +1 -1
- data/lib/trestle/form/fields/collection_radio_buttons.rb +1 -1
- data/lib/trestle/form/fields/date_select.rb +1 -1
- data/lib/trestle/form/fields/datetime_select.rb +1 -1
- data/lib/trestle/form/fields/form_control.rb +2 -2
- data/lib/trestle/form/fields/form_group.rb +4 -4
- data/lib/trestle/form/fields/radio_button.rb +1 -1
- data/lib/trestle/form/fields/static_field.rb +1 -1
- data/lib/trestle/form/fields/time_select.rb +1 -1
- data/lib/trestle/form/renderer.rb +2 -4
- data/lib/trestle/hook/helpers.rb +21 -0
- data/lib/trestle/navigation/block.rb +8 -15
- data/lib/trestle/navigation/group.rb +2 -2
- data/lib/trestle/navigation/item.rb +21 -4
- data/lib/trestle/registry.rb +14 -11
- data/lib/trestle/resource/builder.rb +9 -6
- data/lib/trestle/resource/toolbar.rb +4 -4
- data/lib/trestle/resource.rb +7 -5
- data/lib/trestle/scopes/block.rb +8 -12
- data/lib/trestle/scopes/definition.rb +6 -2
- data/lib/trestle/scopes/scope.rb +13 -10
- data/lib/trestle/tab.rb +2 -2
- data/lib/trestle/table/column.rb +4 -3
- data/lib/trestle/table/row.rb +1 -1
- data/lib/trestle/toolbar/builder.rb +6 -6
- data/lib/trestle/toolbar/context.rb +2 -4
- data/lib/trestle/toolbar/item.rb +10 -19
- data/lib/trestle/toolbar/menu.rb +9 -9
- data/lib/trestle/version.rb +1 -1
- data/lib/trestle.rb +2 -2
- data/package.json +7 -7
- data/yarn.lock +517 -564
- metadata +5 -5
- data/app/assets/bundle/trestle/photoswipe-063ce7be40e10b3e6848.digested.js +0 -6
- data/app/views/layouts/trestle/admin.turbo_stream.erb +0 -4
@@ -109,7 +109,7 @@ export default class extends ApplicationController {
|
|
109
109
|
itemData.type = 'html'
|
110
110
|
itemData.html = `<video controls><source src="${itemData.src}"></video>`
|
111
111
|
|
112
|
-
this
|
112
|
+
this.#setDefaultVideoDimensions(itemData)
|
113
113
|
}
|
114
114
|
|
115
115
|
return itemData
|
@@ -126,7 +126,7 @@ export default class extends ApplicationController {
|
|
126
126
|
itemData.type = 'html'
|
127
127
|
itemData.html = `<iframe src="${src}" allowfullscreen></iframe>`
|
128
128
|
|
129
|
-
this
|
129
|
+
this.#setDefaultVideoDimensions(itemData)
|
130
130
|
}
|
131
131
|
}
|
132
132
|
|
@@ -151,7 +151,7 @@ export default class extends ApplicationController {
|
|
151
151
|
}
|
152
152
|
}
|
153
153
|
|
154
|
-
setDefaultVideoDimensions(itemData) {
|
154
|
+
#setDefaultVideoDimensions(itemData) {
|
155
155
|
itemData.w ||= this.defaultVideoWidthValue
|
156
156
|
itemData.h ||= this.defaultVideoHeightValue
|
157
157
|
}
|
@@ -26,7 +26,7 @@ export default class extends ApplicationController {
|
|
26
26
|
.then((modal) => {
|
27
27
|
this.modal = modal
|
28
28
|
|
29
|
-
const modalController = this
|
29
|
+
const modalController = this.#getModalController(modal)
|
30
30
|
modalController.modalTrigger = this
|
31
31
|
|
32
32
|
this.dispatch('loaded', { detail: modal })
|
@@ -68,7 +68,7 @@ export default class extends ApplicationController {
|
|
68
68
|
return this.element.nodeName === 'A'
|
69
69
|
}
|
70
70
|
|
71
|
-
|
71
|
+
#getModalController (modal) {
|
72
72
|
return this.application.getControllerForElementAndIdentifier(modal, 'modal')
|
73
73
|
}
|
74
74
|
}
|
@@ -5,6 +5,10 @@ import cookie from '../core/cookie'
|
|
5
5
|
export default class extends ApplicationController {
|
6
6
|
static targets = ["inner"]
|
7
7
|
|
8
|
+
static values = {
|
9
|
+
scrollMargin: { type: Number, default: 100 }
|
10
|
+
}
|
11
|
+
|
8
12
|
connect () {
|
9
13
|
this.scrollToActive()
|
10
14
|
}
|
@@ -25,9 +29,15 @@ export default class extends ApplicationController {
|
|
25
29
|
}
|
26
30
|
|
27
31
|
scrollToActive () {
|
28
|
-
|
29
|
-
|
30
|
-
|
32
|
+
if (!this.hasInnerTarget) return
|
33
|
+
|
34
|
+
const active = this.element.querySelector('.active')
|
35
|
+
if (!active) return
|
36
|
+
|
37
|
+
// Check if bottom of active element is outside of visible navigation height (plus scroll margin)
|
38
|
+
const activeOffset = active.offsetTop + active.offsetHeight + this.scrollMarginValue
|
39
|
+
if (activeOffset > this.innerTarget.clientHeight) {
|
40
|
+
this.innerTarget.scrollTop = activeOffset - this.innerTarget.clientHeight
|
31
41
|
}
|
32
42
|
}
|
33
43
|
}
|
@@ -20,7 +20,7 @@ export default class extends ApplicationController {
|
|
20
20
|
const errorCount = pane.querySelectorAll(this.errorSelectorValue).length
|
21
21
|
|
22
22
|
if (errorCount > 0) {
|
23
|
-
const badge = this
|
23
|
+
const badge = this.#createErrorBadge(errorCount)
|
24
24
|
link.appendChild(badge)
|
25
25
|
}
|
26
26
|
}
|
@@ -34,7 +34,7 @@ export default class extends ApplicationController {
|
|
34
34
|
})
|
35
35
|
}
|
36
36
|
|
37
|
-
|
37
|
+
#createErrorBadge (count) {
|
38
38
|
const badge = document.createElement('span')
|
39
39
|
|
40
40
|
badge.classList.add('badge', 'badge-danger', 'badge-pill')
|
@@ -12,6 +12,10 @@ const CLASS_NAME_SHOW = 'show'
|
|
12
12
|
const CLASS_NAME_LOADING = 'loading'
|
13
13
|
|
14
14
|
export default class Backdrop {
|
15
|
+
#config
|
16
|
+
#element
|
17
|
+
#isAppended
|
18
|
+
|
15
19
|
static getInstance () {
|
16
20
|
if (!this.instance) {
|
17
21
|
this.instance = new Backdrop()
|
@@ -21,31 +25,31 @@ export default class Backdrop {
|
|
21
25
|
}
|
22
26
|
|
23
27
|
constructor () {
|
24
|
-
this
|
25
|
-
this
|
26
|
-
this
|
28
|
+
this.#config = Default
|
29
|
+
this.#element = null
|
30
|
+
this.#isAppended = false
|
27
31
|
}
|
28
32
|
|
29
33
|
show (callback) {
|
30
|
-
this
|
34
|
+
this.#append()
|
31
35
|
|
32
|
-
if (this.
|
33
|
-
reflow(this
|
36
|
+
if (this.#config.isAnimated) {
|
37
|
+
reflow(this.#getElement())
|
34
38
|
}
|
35
39
|
|
36
|
-
this
|
40
|
+
this.#getElement().classList.add(CLASS_NAME_SHOW)
|
37
41
|
|
38
|
-
this
|
42
|
+
this.#emulateAnimation(() => {
|
39
43
|
execute(callback)
|
40
44
|
})
|
41
45
|
}
|
42
46
|
|
43
47
|
hide (callback) {
|
44
48
|
if (Modal.existing.length === 0) {
|
45
|
-
this
|
49
|
+
this.#getElement().classList.remove(CLASS_NAME_SHOW)
|
46
50
|
}
|
47
51
|
|
48
|
-
this
|
52
|
+
this.#emulateAnimation(() => {
|
49
53
|
this.dispose()
|
50
54
|
execute(callback)
|
51
55
|
Modal.restorePrevious()
|
@@ -53,48 +57,46 @@ export default class Backdrop {
|
|
53
57
|
}
|
54
58
|
|
55
59
|
dispose () {
|
56
|
-
if (!this
|
60
|
+
if (!this.#isAppended) {
|
57
61
|
return
|
58
62
|
}
|
59
63
|
|
60
64
|
if (Modal.existing.length === 0) {
|
61
|
-
this.
|
62
|
-
this
|
65
|
+
this.#element.remove()
|
66
|
+
this.#isAppended = false
|
63
67
|
}
|
64
68
|
}
|
65
69
|
|
66
70
|
loading (isLoading) {
|
67
|
-
const el = this
|
71
|
+
const el = this.#getElement()
|
68
72
|
el.classList[isLoading ? 'add' : 'remove'](CLASS_NAME_LOADING)
|
69
73
|
}
|
70
74
|
|
71
|
-
|
72
|
-
|
73
|
-
_getElement () {
|
74
|
-
if (!this._element) {
|
75
|
+
#getElement () {
|
76
|
+
if (!this.#element) {
|
75
77
|
const backdrop = document.createElement('div')
|
76
|
-
backdrop.className = this.
|
77
|
-
if (this.
|
78
|
+
backdrop.className = this.#config.className
|
79
|
+
if (this.#config.isAnimated) {
|
78
80
|
backdrop.classList.add(CLASS_NAME_FADE)
|
79
81
|
}
|
80
82
|
|
81
|
-
this
|
83
|
+
this.#element = backdrop
|
82
84
|
}
|
83
85
|
|
84
|
-
return this
|
86
|
+
return this.#element
|
85
87
|
}
|
86
88
|
|
87
|
-
|
88
|
-
if (this
|
89
|
+
#append () {
|
90
|
+
if (this.#isAppended) {
|
89
91
|
return
|
90
92
|
}
|
91
93
|
|
92
|
-
document.body.append(this
|
94
|
+
document.body.append(this.#getElement())
|
93
95
|
|
94
|
-
this
|
96
|
+
this.#isAppended = true
|
95
97
|
}
|
96
98
|
|
97
|
-
|
98
|
-
executeAfterTransition(callback, this
|
99
|
+
#emulateAnimation (callback) {
|
100
|
+
executeAfterTransition(callback, this.#getElement(), this.#config.isAnimated)
|
99
101
|
}
|
100
102
|
}
|
@@ -33,33 +33,31 @@ export default class ErrorModal {
|
|
33
33
|
}
|
34
34
|
|
35
35
|
show () {
|
36
|
-
this
|
36
|
+
this.#append(this.#buildModal())
|
37
37
|
}
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
_buildModal () {
|
42
|
-
const el = this._buildWrapper()
|
39
|
+
#buildModal () {
|
40
|
+
const el = this.#buildWrapper()
|
43
41
|
el.querySelector('.modal-title').textContent = this.title
|
44
42
|
|
45
|
-
const iframe = this
|
43
|
+
const iframe = this.#buildIframe(this.content)
|
46
44
|
el.querySelector('.modal-body').append(iframe)
|
47
45
|
|
48
46
|
return el
|
49
47
|
}
|
50
48
|
|
51
|
-
|
49
|
+
#buildWrapper () {
|
52
50
|
return new DOMParser().parseFromString(TEMPLATE(), 'text/html').body.childNodes[0]
|
53
51
|
}
|
54
52
|
|
55
|
-
|
53
|
+
#buildIframe () {
|
56
54
|
const iframe = document.createElement('iframe')
|
57
55
|
iframe.className = 'error-iframe'
|
58
56
|
iframe.srcdoc = this.content
|
59
57
|
return iframe
|
60
58
|
}
|
61
59
|
|
62
|
-
|
60
|
+
#append (el) {
|
63
61
|
document.getElementById('modal').append(el)
|
64
62
|
}
|
65
63
|
}
|
data/frontend/js/core/fetch.js
CHANGED
@@ -13,6 +13,7 @@ export function fetchWithErrorHandling (url, options = {}) {
|
|
13
13
|
.catch(response => {
|
14
14
|
const title = `${response.status} (${response.statusText})`
|
15
15
|
response.text().then(content => ErrorModal.show({ title, content }))
|
16
|
+
throw response
|
16
17
|
})
|
17
18
|
}
|
18
19
|
|
@@ -29,4 +30,5 @@ export function fetchTurboStream (url, options = {}) {
|
|
29
30
|
return fetchWithErrorHandling(url, options)
|
30
31
|
.then(response => response.text())
|
31
32
|
.then(html => renderStreamMessage(html))
|
33
|
+
.catch(() => { /* Error already handled */ })
|
32
34
|
}
|
data/lib/trestle/admin.rb
CHANGED
@@ -14,6 +14,7 @@ module Trestle
|
|
14
14
|
super
|
15
15
|
end
|
16
16
|
end
|
17
|
+
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
|
17
18
|
|
18
19
|
def respond_to_missing?(name, include_private=false)
|
19
20
|
self.class.respond_to?(name, include_private) || super
|
@@ -118,8 +119,14 @@ module Trestle
|
|
118
119
|
"#{name.underscore}/admin"
|
119
120
|
end
|
120
121
|
|
121
|
-
def path(action=
|
122
|
-
|
122
|
+
def path(action=nil, options={})
|
123
|
+
defaults = {
|
124
|
+
controller: controller_namespace,
|
125
|
+
action: action || root_action,
|
126
|
+
only_path: true
|
127
|
+
}
|
128
|
+
|
129
|
+
Engine.routes.url_for(defaults.merge(options))
|
123
130
|
end
|
124
131
|
|
125
132
|
def to_param(*)
|
@@ -91,6 +91,9 @@ module Trestle
|
|
91
91
|
# Default adapter class used by all admin resources
|
92
92
|
option :default_adapter, Adapters.compose(Adapters::ActiveRecordAdapter, Adapters::DraperAdapter)
|
93
93
|
|
94
|
+
# List of Stimulus controllers to add to forms by default
|
95
|
+
option :default_form_controllers, %w(keyboard-submit form-loading form-error)
|
96
|
+
|
94
97
|
# Register a custom form field class
|
95
98
|
def form_field(name, field)
|
96
99
|
Form::Builder.register(name, field)
|
data/lib/trestle/engine.rb
CHANGED
@@ -5,20 +5,18 @@ module Trestle
|
|
5
5
|
# both the Adapter/Navigation instance, as well as the controller/view from where they are invoked.
|
6
6
|
module EvaluationContext
|
7
7
|
protected
|
8
|
-
def self.ruby2_keywords(*)
|
9
|
-
end unless respond_to?(:ruby2_keywords, true)
|
10
|
-
|
11
8
|
# Missing methods are called on the given context if available.
|
12
9
|
#
|
13
10
|
# We include private methods as methods such as current_user
|
14
11
|
# are usually declared as private or protected.
|
15
|
-
|
12
|
+
def method_missing(name, *args, &block)
|
16
13
|
if context_responds_to?(name)
|
17
14
|
@context.send(name, *args, &block)
|
18
15
|
else
|
19
16
|
super
|
20
17
|
end
|
21
18
|
end
|
19
|
+
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
|
22
20
|
|
23
21
|
def respond_to_missing?(name, include_private=false)
|
24
22
|
context_responds_to?(name) || super
|
@@ -16,7 +16,7 @@ module Trestle
|
|
16
16
|
if associated_instance = instance.public_send(attribute.association_name)
|
17
17
|
admin_link_to format_value(associated_instance), associated_instance
|
18
18
|
else
|
19
|
-
|
19
|
+
tag.span(I18n.t("admin.format.blank"), class: "blank")
|
20
20
|
end
|
21
21
|
end
|
22
22
|
else
|
data/lib/trestle/form/field.rb
CHANGED
@@ -3,7 +3,7 @@ module Trestle
|
|
3
3
|
class Field
|
4
4
|
attr_reader :builder, :template, :name, :options, :block
|
5
5
|
|
6
|
-
delegate :admin, :content_tag, :concat, :safe_join, :icon, to: :template
|
6
|
+
delegate :admin, :tag, :content_tag, :concat, :safe_join, :icon, to: :template
|
7
7
|
|
8
8
|
def initialize(builder, template, name, options={}, &block)
|
9
9
|
@builder, @template, @name, @block = builder, template, name, block
|
@@ -19,7 +19,7 @@ module Trestle
|
|
19
19
|
wrapper_class = options.delete(:class)
|
20
20
|
wrapper_class = default_wrapper_class if wrapper_class.empty?
|
21
21
|
|
22
|
-
|
22
|
+
tag.div(class: wrapper_class) do
|
23
23
|
safe_join([
|
24
24
|
builder.raw_check_box(name, options.merge(class: input_class), checked_value, unchecked_value),
|
25
25
|
builder.label(name, options[:label] || admin.human_attribute_name(name), class: label_class, value: (checked_value if options[:multiple]))
|
@@ -12,7 +12,7 @@ module Trestle
|
|
12
12
|
|
13
13
|
def input_group
|
14
14
|
if @prepend || @append
|
15
|
-
|
15
|
+
tag.div(class: "input-group") do
|
16
16
|
concat input_group_addon(@prepend) if @prepend
|
17
17
|
concat yield
|
18
18
|
concat input_group_addon(@append) if @append
|
@@ -24,7 +24,7 @@ module Trestle
|
|
24
24
|
|
25
25
|
def input_group_addon(addon)
|
26
26
|
if addon[:wrap]
|
27
|
-
|
27
|
+
tag.span(addon[:content], class: "input-group-text")
|
28
28
|
else
|
29
29
|
addon[:content]
|
30
30
|
end
|
@@ -12,7 +12,7 @@ module Trestle
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def render
|
15
|
-
|
15
|
+
tag.div(**options.except(*WRAPPER_OPTIONS)) do
|
16
16
|
concat label if name && options[:label] != false
|
17
17
|
concat template.capture(&block) if block
|
18
18
|
concat help_message if options[:help]
|
@@ -30,13 +30,13 @@ module Trestle
|
|
30
30
|
message = options[:help]
|
31
31
|
end
|
32
32
|
|
33
|
-
|
33
|
+
tag.p(message, class: classes)
|
34
34
|
end
|
35
35
|
|
36
36
|
def error_messages
|
37
|
-
|
37
|
+
tag.ul(class: "invalid-feedback") do
|
38
38
|
safe_join(errors.map { |error|
|
39
|
-
|
39
|
+
tag.li(safe_join([icon("fa fa-warning"), error], " "))
|
40
40
|
}, "\n")
|
41
41
|
end
|
42
42
|
end
|
@@ -20,7 +20,7 @@ module Trestle
|
|
20
20
|
wrapper_class = options.delete(:class)
|
21
21
|
wrapper_class = default_wrapper_class if wrapper_class.empty?
|
22
22
|
|
23
|
-
|
23
|
+
tag.div(class: wrapper_class) do
|
24
24
|
safe_join([
|
25
25
|
builder.raw_radio_button(name, tag_value, options.merge(class: input_class)),
|
26
26
|
builder.label(name, options[:label] || tag_value.to_s.humanize, value: tag_value, class: label_class)
|
@@ -4,9 +4,6 @@ require "action_view/helpers"
|
|
4
4
|
module Trestle
|
5
5
|
class Form
|
6
6
|
class Renderer
|
7
|
-
def self.ruby2_keywords(*)
|
8
|
-
end unless respond_to?(:ruby2_keywords, true)
|
9
|
-
|
10
7
|
include ::ActionView::Context
|
11
8
|
include ::ActionView::Helpers::CaptureHelper
|
12
9
|
|
@@ -46,7 +43,7 @@ module Trestle
|
|
46
43
|
concat(result)
|
47
44
|
end
|
48
45
|
|
49
|
-
|
46
|
+
def method_missing(name, *args, &block)
|
50
47
|
target = @form.respond_to?(name) ? @form : @template
|
51
48
|
|
52
49
|
if block_given? && !RAW_BLOCK_HELPERS.include?(name)
|
@@ -63,6 +60,7 @@ module Trestle
|
|
63
60
|
result
|
64
61
|
end
|
65
62
|
end
|
63
|
+
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
|
66
64
|
|
67
65
|
def respond_to_missing?(name, include_all=false)
|
68
66
|
@form.respond_to?(name, include_all) ||
|
data/lib/trestle/hook/helpers.rb
CHANGED
@@ -1,6 +1,25 @@
|
|
1
1
|
module Trestle
|
2
2
|
class Hook
|
3
3
|
module Helpers
|
4
|
+
# Evaluates any defined hooks with the given name and returns the result.
|
5
|
+
#
|
6
|
+
# Each hook is evaluated and passed any provided arguments, and the result
|
7
|
+
# is concatenated together. If no hooks are defined, and a block is passed
|
8
|
+
# to this helper, then the block will be evaluated instead.
|
9
|
+
#
|
10
|
+
# name - Name of hook to evaluate
|
11
|
+
# args - Arguments to pass to hook blocks
|
12
|
+
# block - Optional block to evaluate as a fallback if no hooks are defined
|
13
|
+
#
|
14
|
+
# Examples
|
15
|
+
#
|
16
|
+
# <%= hook("index.toolbar.primary", toolbar) %>
|
17
|
+
#
|
18
|
+
# <%= hook("view.title") do %>
|
19
|
+
# Default Title
|
20
|
+
# <% end %>
|
21
|
+
#
|
22
|
+
# Returns a HTML-safe string.
|
4
23
|
def hook(name, *args, &block)
|
5
24
|
hooks = hooks(name)
|
6
25
|
|
@@ -13,6 +32,8 @@ module Trestle
|
|
13
32
|
end
|
14
33
|
end
|
15
34
|
|
35
|
+
# Returns true or false depending on whether there are any defined hooks
|
36
|
+
# (either on the current admin or globally) with the given name.
|
16
37
|
def hook?(name)
|
17
38
|
hooks(name).any?
|
18
39
|
end
|
@@ -17,6 +17,8 @@ module Trestle
|
|
17
17
|
class Evaluator
|
18
18
|
include EvaluationContext
|
19
19
|
|
20
|
+
delegate :path, to: :@admin
|
21
|
+
|
20
22
|
attr_reader :items
|
21
23
|
|
22
24
|
def initialize(admin=nil, context=nil)
|
@@ -24,30 +26,21 @@ module Trestle
|
|
24
26
|
@items = []
|
25
27
|
end
|
26
28
|
|
27
|
-
def
|
28
|
-
@admin ? @admin.path : nil
|
29
|
-
end
|
30
|
-
|
31
|
-
def item(name, path=nil, options={})
|
32
|
-
if path.is_a?(Hash)
|
33
|
-
options = path
|
34
|
-
path = nil
|
35
|
-
end
|
36
|
-
|
29
|
+
def item(name, path=nil, **options)
|
37
30
|
if options[:group]
|
38
31
|
group = Group.new(options[:group])
|
39
32
|
elsif @current_group
|
40
33
|
group = @current_group
|
41
34
|
end
|
42
35
|
|
43
|
-
options
|
44
|
-
options
|
36
|
+
options.merge!(group: group) if group
|
37
|
+
options.merge!(admin: @admin) if @admin
|
45
38
|
|
46
|
-
items << Item.new(name, path
|
39
|
+
items << Item.new(name, path, **options)
|
47
40
|
end
|
48
41
|
|
49
|
-
def group(name, options
|
50
|
-
@current_group = Group.new(name, options)
|
42
|
+
def group(name, **options)
|
43
|
+
@current_group = Group.new(name, **options)
|
51
44
|
yield
|
52
45
|
ensure
|
53
46
|
@current_group = nil
|
@@ -3,7 +3,7 @@ module Trestle
|
|
3
3
|
class Group
|
4
4
|
attr_reader :name, :options
|
5
5
|
|
6
|
-
def initialize(name, options
|
6
|
+
def initialize(name, **options)
|
7
7
|
@name, @options = name.to_s, options
|
8
8
|
end
|
9
9
|
|
@@ -26,7 +26,7 @@ module Trestle
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def merge(other)
|
29
|
-
self.class.new(name, options.merge(other.options))
|
29
|
+
self.class.new(name, **options.merge(other.options))
|
30
30
|
end
|
31
31
|
|
32
32
|
def priority
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Trestle
|
2
2
|
class Navigation
|
3
3
|
class Item
|
4
|
-
attr_reader :name, :
|
4
|
+
attr_reader :name, :options
|
5
5
|
|
6
|
-
def initialize(name, path=nil, options
|
6
|
+
def initialize(name, path=nil, **options)
|
7
7
|
@name, @path, @options = name.to_s, path, options
|
8
8
|
end
|
9
9
|
|
@@ -35,8 +35,25 @@ module Trestle
|
|
35
35
|
options[:group] || NullGroup.new
|
36
36
|
end
|
37
37
|
|
38
|
+
def path
|
39
|
+
if @path
|
40
|
+
@path
|
41
|
+
elsif admin = self.admin
|
42
|
+
admin.path(options[:action])
|
43
|
+
else
|
44
|
+
"#"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
38
48
|
def admin
|
39
|
-
options[:admin]
|
49
|
+
case options[:admin]
|
50
|
+
when nil, false
|
51
|
+
return
|
52
|
+
when Symbol, String
|
53
|
+
Trestle.lookup(options[:admin]) or raise ActionController::UrlGenerationError, "No admin found named #{options[:admin].inspect}"
|
54
|
+
else
|
55
|
+
options[:admin]
|
56
|
+
end
|
40
57
|
end
|
41
58
|
|
42
59
|
def label
|
@@ -56,7 +73,7 @@ module Trestle
|
|
56
73
|
end
|
57
74
|
|
58
75
|
def html_options
|
59
|
-
options.except(:admin, :badge, :group, :icon, :if, :label, :priority, :unless)
|
76
|
+
options.except(:action, :admin, :badge, :group, :icon, :if, :label, :priority, :unless)
|
60
77
|
end
|
61
78
|
|
62
79
|
def visible?(context)
|