trestle 0.10.0.pre2 → 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/Gemfile +2 -2
- data/README.md +1 -1
- data/app/assets/bundle/trestle/admin.css +9 -9
- data/app/assets/bundle/trestle/admin.js +13 -13
- data/app/assets/bundle/trestle/fa-brands-400.ttf +0 -0
- data/app/assets/bundle/trestle/fa-brands-400.woff2 +0 -0
- data/app/assets/bundle/trestle/fa-regular-400.ttf +0 -0
- data/app/assets/bundle/trestle/fa-regular-400.woff2 +0 -0
- data/app/assets/bundle/trestle/fa-solid-900.ttf +0 -0
- data/app/assets/bundle/trestle/fa-solid-900.woff2 +0 -0
- data/app/assets/bundle/trestle/photoswipe-2d522a3abaa59f8a8f73.digested.js +6 -0
- data/app/assets/sprockets/trestle/icons/font-awesome.css.erb +1 -1
- data/app/controllers/concerns/trestle/controller/modal.rb +3 -1
- 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 +9 -5
- data/frontend/css/variables/_trestle.scss +3 -0
- data/frontend/js/controllers/confirm_delete_controller.js +4 -4
- data/frontend/js/controllers/flatpickr_controller.js +2 -2
- data/frontend/js/controllers/gallery_controller.js +2 -0
- data/frontend/js/controllers/lightbox_controller.js +3 -5
- 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 +3 -5
- 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 +5 -5
- 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/trestle.gemspec +1 -1
- data/yarn.lock +751 -796
- metadata +7 -7
- data/app/assets/bundle/trestle/photoswipe-7aa1aec9c3c1fd65c382.digested.js +0 -6
- data/app/views/layouts/trestle/admin.turbo_stream.erb +0 -4
@@ -3,6 +3,8 @@
|
|
3
3
|
.app-wrapper {
|
4
4
|
--sidebar-width: #{$sidebar-width};
|
5
5
|
--sidebar-width-collapsed: #{$sidebar-width-collapsed};
|
6
|
+
--sidebar-padding: #{$sidebar-padding};
|
7
|
+
--sidebar-padding-collapsed: #{$sidebar-padding-collapsed};
|
6
8
|
--sidebar-transition-duration: #{$sidebar-transition-duration};
|
7
9
|
--sidebar-transition-timing: ease-out;
|
8
10
|
--sidebar-bg: #{$sidebar-bg};
|
@@ -69,7 +71,7 @@
|
|
69
71
|
display: flex;
|
70
72
|
align-items: center;
|
71
73
|
|
72
|
-
padding:
|
74
|
+
padding: var(--sidebar-padding);
|
73
75
|
|
74
76
|
font-size: 1.25rem;
|
75
77
|
font-weight: 500;
|
@@ -100,6 +102,8 @@
|
|
100
102
|
display: flex;
|
101
103
|
flex-direction: column;
|
102
104
|
|
105
|
+
position: relative;
|
106
|
+
|
103
107
|
overflow-x: hidden;
|
104
108
|
overflow-y: auto;
|
105
109
|
|
@@ -176,7 +180,7 @@
|
|
176
180
|
|
177
181
|
.app-sidebar-title {
|
178
182
|
justify-content: center;
|
179
|
-
padding:
|
183
|
+
padding: var(--sidebar-padding-collapsed);
|
180
184
|
|
181
185
|
// Match right margin with navbar toggler width:
|
182
186
|
// (margin + border + font-size * icon-width + padding)
|
@@ -200,7 +204,7 @@
|
|
200
204
|
width: var(--sidebar-width-collapsed);
|
201
205
|
flex-basis: var(--sidebar-width-collapsed);
|
202
206
|
|
203
|
-
padding:
|
207
|
+
padding: var(--sidebar-padding-collapsed);
|
204
208
|
justify-content: center;
|
205
209
|
|
206
210
|
img {
|
@@ -231,7 +235,7 @@
|
|
231
235
|
width: var(--sidebar-width);
|
232
236
|
flex-basis: var(--sidebar-width);
|
233
237
|
|
234
|
-
padding:
|
238
|
+
padding: var(--sidebar-padding);
|
235
239
|
justify-content: flex-start;
|
236
240
|
|
237
241
|
img {
|
@@ -267,7 +271,7 @@
|
|
267
271
|
width: var(--sidebar-width-collapsed);
|
268
272
|
flex-basis: var(--sidebar-width-collapsed);
|
269
273
|
|
270
|
-
padding:
|
274
|
+
padding: var(--sidebar-padding-collapsed);
|
271
275
|
justify-content: center;
|
272
276
|
|
273
277
|
img {
|
@@ -61,6 +61,9 @@ $sidebar-mobile-toggle-active-border: $sidebar-mobile-toggle-border-width
|
|
61
61
|
$sidebar-width: 15.625rem;
|
62
62
|
$sidebar-width-collapsed: 3.5rem;
|
63
63
|
|
64
|
+
$sidebar-padding: 0.5rem 0.875rem;
|
65
|
+
$sidebar-padding-collapsed: 0.5rem 0.25rem;
|
66
|
+
|
64
67
|
$sidebar-transition-duration: 200ms;
|
65
68
|
|
66
69
|
|
@@ -3,9 +3,9 @@ import ConfirmController from './confirm_controller'
|
|
3
3
|
import { i18n } from '../core/i18n'
|
4
4
|
|
5
5
|
export default class extends ConfirmController {
|
6
|
-
static values = {
|
7
|
-
confirmLabel: i18n['trestle.confirmation.delete'] || 'Delete'
|
8
|
-
}
|
9
|
-
|
10
6
|
static defaultConfirmClass = 'btn-danger'
|
7
|
+
|
8
|
+
get confirmLabel () {
|
9
|
+
return this.confirmLabelValue || i18n.t('trestle.confirmation.delete', { defaultValue: 'Delete' })
|
10
|
+
}
|
11
11
|
}
|
@@ -23,7 +23,7 @@ export default class extends ApplicationController {
|
|
23
23
|
}
|
24
24
|
|
25
25
|
setup (selectedDates, dateStr, instance) {
|
26
|
-
this
|
26
|
+
this.#createClearButton(instance)
|
27
27
|
}
|
28
28
|
|
29
29
|
get options () {
|
@@ -33,7 +33,7 @@ export default class extends ApplicationController {
|
|
33
33
|
}
|
34
34
|
}
|
35
35
|
|
36
|
-
|
36
|
+
#createClearButton (instance) {
|
37
37
|
if (this.element.dataset.allowClear) {
|
38
38
|
const clearButton = document.createElement('button')
|
39
39
|
|
@@ -26,8 +26,6 @@ const VideoIframeUrl = {
|
|
26
26
|
}
|
27
27
|
|
28
28
|
export default class extends ApplicationController {
|
29
|
-
static targets = ["image"]
|
30
|
-
|
31
29
|
static values = {
|
32
30
|
animationType: { type: String, default: 'zoom' },
|
33
31
|
animationDuration: { type: Number, default: 150 },
|
@@ -111,7 +109,7 @@ export default class extends ApplicationController {
|
|
111
109
|
itemData.type = 'html'
|
112
110
|
itemData.html = `<video controls><source src="${itemData.src}"></video>`
|
113
111
|
|
114
|
-
this
|
112
|
+
this.#setDefaultVideoDimensions(itemData)
|
115
113
|
}
|
116
114
|
|
117
115
|
return itemData
|
@@ -128,7 +126,7 @@ export default class extends ApplicationController {
|
|
128
126
|
itemData.type = 'html'
|
129
127
|
itemData.html = `<iframe src="${src}" allowfullscreen></iframe>`
|
130
128
|
|
131
|
-
this
|
129
|
+
this.#setDefaultVideoDimensions(itemData)
|
132
130
|
}
|
133
131
|
}
|
134
132
|
|
@@ -153,7 +151,7 @@ export default class extends ApplicationController {
|
|
153
151
|
}
|
154
152
|
}
|
155
153
|
|
156
|
-
setDefaultVideoDimensions(itemData) {
|
154
|
+
#setDefaultVideoDimensions(itemData) {
|
157
155
|
itemData.w ||= this.defaultVideoWidthValue
|
158
156
|
itemData.h ||= this.defaultVideoHeightValue
|
159
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
|
|
@@ -14,7 +11,7 @@ module Trestle
|
|
14
11
|
include Hook::Helpers
|
15
12
|
|
16
13
|
# Whitelisted helpers will concatenate their result to the output buffer when called.
|
17
|
-
WHITELISTED_HELPERS = [:row, :col, :render, :tab, :table, :divider, :h1, :h2, :h3, :h4, :h5, :h6, :card, :panel, :well]
|
14
|
+
WHITELISTED_HELPERS = [:row, :col, :render, :tab, :table, :divider, :h1, :h2, :h3, :h4, :h5, :h6, :card, :panel, :well, :turbo_frame_tag]
|
18
15
|
|
19
16
|
# Raw block helpers will pass their block argument directly to the method without wrapping it in a new output buffer.
|
20
17
|
RAW_BLOCK_HELPERS = [:table, :toolbar]
|
@@ -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) ||
|