spina 2.17.0 → 2.19.0
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.
Potentially problematic release.
This version of spina might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/javascripts/spina/controllers/data_binding_controller.js +1 -0
- data/app/assets/javascripts/spina/controllers/page_select_controller.js +2 -37
- data/app/assets/javascripts/spina/controllers/reveal_controller.js +1 -1
- data/app/assets/javascripts/spina/controllers/select_controller.js +44 -0
- data/app/assets/javascripts/spina/libraries/stimulus-data-bindings@1.3.2.js +234 -0
- data/app/assets/javascripts/spina/libraries/stimulus-reveal@1.4.2.js +424 -0
- data/app/controllers/spina/admin/resource_select_options_controller.rb +20 -0
- data/app/helpers/spina/spina_helper.rb +1 -3
- data/app/models/spina/navigation_item.rb +5 -1
- data/app/models/spina/parts/attachment.rb +4 -1
- data/app/models/spina/parts/image_variant.rb +3 -3
- data/app/models/spina/parts/page_link.rb +3 -2
- data/app/models/spina/parts/resource_link.rb +13 -0
- data/app/views/layouts/spina/admin/application.html.erb +1 -1
- data/app/views/spina/admin/navigation_items/_navigation_item.html.erb +1 -1
- data/app/views/spina/admin/page_select_options/index.html.erb +3 -4
- data/app/views/spina/admin/page_select_options/show.html.erb +1 -1
- data/app/views/spina/admin/pages/_form.html.erb +1 -1
- data/app/views/spina/admin/pages/index.html.erb +7 -0
- data/app/views/spina/admin/parts/page_links/_form.html.erb +17 -14
- data/app/views/spina/admin/parts/resource_links/_form.html.erb +32 -0
- data/app/views/spina/admin/resource_select_options/index.html.erb +13 -0
- data/app/views/spina/admin/resource_select_options/show.html.erb +3 -0
- data/app/views/spina/admin/shared/_navigation.html.erb +3 -1
- data/config/locales/da.yml +418 -0
- data/config/locales/en.yml +8 -5
- data/config/locales/nl.yml +1 -1
- data/config/routes.rb +4 -1
- data/lib/generators/spina/templates/config/initializers/spina.rb +5 -0
- data/lib/spina/engine.rb +2 -1
- data/lib/spina/theme.rb +8 -6
- data/lib/spina/version.rb +1 -1
- data/lib/spina.rb +2 -0
- data/lib/tasks/tailwind.rake +12 -4
- metadata +25 -10
- data/app/assets/javascripts/spina/libraries/stimulus-reveal@1.2.4.js +0 -388
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '086e323d25a5656407e2aa82ddc38880377fbc9b17c133cf871960b93048dbe8'
|
4
|
+
data.tar.gz: db63f3e045d380e2e780123384458b256ed4d2a03c0eaf55ad84ff2f500f4565
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e65a55997c54d92fef58a5e69eb27f5f2e2e642ea0070862b65a718d63e61cfc53622c1c62a2e4d187f9511a8b8adea77f1ae4308447943e17b4ba6c35e73a67
|
7
|
+
data.tar.gz: b42c96e25a501760af09946823032406af80a47b7af0842efc42c0ebdc29d8ed92707b03bd2fbc2484dd6154e2186d0e72880b5ac4dbb41e781189d6a4b95d5f
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
<img src="https://spinacms.com/spinacms.png" alt="Spina CMS" width="225"/>
|
2
2
|
|
3
|
-
[Spina CMS](https://spinacms.com) is an easy to use CMS that features a clean interface without distractions.
|
3
|
+
[Spina CMS](https://spinacms.com) is an easy to use CMS that features a clean interface without distractions.
|
4
4
|
|
5
5
|
[](https://github.com/SpinaCMS/Spina/actions/workflows/ruby.yml)
|
6
6
|
[](https://codeclimate.com/github/SpinaCMS/Spina)
|
@@ -0,0 +1 @@
|
|
1
|
+
//= require ../libraries/stimulus-data-bindings@1.3.2.js
|
@@ -1,38 +1,3 @@
|
|
1
|
-
import
|
1
|
+
import SelectController from 'controllers/select_controller'
|
2
2
|
|
3
|
-
export default class extends
|
4
|
-
|
5
|
-
static get targets() {
|
6
|
-
return ['input', 'label', 'search']
|
7
|
-
}
|
8
|
-
|
9
|
-
connect() {
|
10
|
-
// Show placeholder if there is no page selected yet
|
11
|
-
if (this.labelTarget.querySelector("turbo-frame") == undefined) {
|
12
|
-
this.clear()
|
13
|
-
}
|
14
|
-
}
|
15
|
-
|
16
|
-
select(event) {
|
17
|
-
let button = event.currentTarget
|
18
|
-
|
19
|
-
this.inputTarget.value = button.dataset.id
|
20
|
-
this.labelTarget.innerText = button.dataset.title
|
21
|
-
}
|
22
|
-
|
23
|
-
clear() {
|
24
|
-
this.inputTarget.value = ""
|
25
|
-
this.labelTarget.innerHTML = `
|
26
|
-
<span class="text-gray-400">
|
27
|
-
${this.element.dataset.placeholder}
|
28
|
-
</span>
|
29
|
-
`
|
30
|
-
}
|
31
|
-
|
32
|
-
autofocus() {
|
33
|
-
setTimeout(function() {
|
34
|
-
this.searchTarget.focus()
|
35
|
-
}.bind(this), 100)
|
36
|
-
}
|
37
|
-
|
38
|
-
}
|
3
|
+
export default class extends SelectController { }
|
@@ -1 +1 @@
|
|
1
|
-
//= require ../libraries/stimulus-reveal@1.2.
|
1
|
+
//= require ../libraries/stimulus-reveal@1.4.2.js
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
|
5
|
+
static get targets() {
|
6
|
+
return ['input', 'label', 'search']
|
7
|
+
}
|
8
|
+
|
9
|
+
connect() {
|
10
|
+
// Show placeholder if there is no page selected yet
|
11
|
+
if (this.labelTarget.querySelector("turbo-frame") == undefined) {
|
12
|
+
this.clear()
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
select(event) {
|
17
|
+
let button = event.currentTarget
|
18
|
+
|
19
|
+
this.inputTarget.value = button.dataset.id
|
20
|
+
this.labelTarget.innerText = button.dataset.title
|
21
|
+
this._dispatchChange()
|
22
|
+
}
|
23
|
+
|
24
|
+
clear() {
|
25
|
+
this.inputTarget.value = ""
|
26
|
+
this.labelTarget.innerHTML = `
|
27
|
+
<span class="text-gray-400">
|
28
|
+
${this.element.dataset.placeholder}
|
29
|
+
</span>
|
30
|
+
`
|
31
|
+
this._dispatchChange()
|
32
|
+
}
|
33
|
+
|
34
|
+
autofocus() {
|
35
|
+
setTimeout(function () {
|
36
|
+
this.searchTarget.focus()
|
37
|
+
}.bind(this), 100)
|
38
|
+
}
|
39
|
+
|
40
|
+
_dispatchChange() {
|
41
|
+
this.inputTarget.dispatchEvent(new Event('change', { bubbles: true, cancelable: false }))
|
42
|
+
}
|
43
|
+
|
44
|
+
}
|
@@ -0,0 +1,234 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
/**
|
4
|
+
* One way data and visibility bindings for inputs
|
5
|
+
* @extends Controller
|
6
|
+
*/
|
7
|
+
export default class DataBindingController extends Controller {
|
8
|
+
/**
|
9
|
+
* Initialize bindings on connection to the DOM
|
10
|
+
*/
|
11
|
+
connect() {
|
12
|
+
if (this.element.dataset.bindingDebug === "true") {
|
13
|
+
this.debugMode = true
|
14
|
+
}
|
15
|
+
|
16
|
+
this._debug("stimulus-data-binding: connecting to wrapper:", this.element)
|
17
|
+
|
18
|
+
const sourceElements = Array.from(this.element.querySelectorAll('[data-binding-target]'))
|
19
|
+
if (this.element.dataset.bindingTarget) sourceElements.unshift(this.element)
|
20
|
+
|
21
|
+
if (sourceElements.length === 0) this._debug("No source elements found. Did you set data-binding-target on your source elements?")
|
22
|
+
|
23
|
+
for (const sourceElement of sourceElements) {
|
24
|
+
if (this.debugMode) console.group("stimulus-data-binding: Source element")
|
25
|
+
this._debug("Source element found", sourceElement)
|
26
|
+
|
27
|
+
if (sourceElement.dataset.bindingInitial !== 'false') {
|
28
|
+
this._debug("Running initial binding on source element")
|
29
|
+
this._runBindings(sourceElement)
|
30
|
+
} else {
|
31
|
+
this._debug("%cNot running initial binding on source element as binding-initial is set to false", "color: rgba(150,150,150,0.8);")
|
32
|
+
}
|
33
|
+
|
34
|
+
if (this.debugMode) console.groupEnd()
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Updates bindings for the current element.
|
40
|
+
* @param {Event} e - an event with a currentTarget DOMElement
|
41
|
+
*/
|
42
|
+
update(e) {
|
43
|
+
this._runBindings(e.currentTarget)
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* @private
|
48
|
+
* @param {DOMElement} source
|
49
|
+
*/
|
50
|
+
_runBindings(source) {
|
51
|
+
this._debug("Searching for targets for source: ", source)
|
52
|
+
for (const targetRef of source.dataset.bindingTarget.split(' ')) {
|
53
|
+
const targetElements = this._bindingElements(targetRef)
|
54
|
+
|
55
|
+
if (targetElements.length === 0) this._debug(`Could not find any target elements for ref ${targetRef}. Have you set data-target-ref="${targetRef}" on your target elements?`)
|
56
|
+
|
57
|
+
for (const target of targetElements) {
|
58
|
+
if (this.debugMode) console.group("stimulus-data-binding: Target Element")
|
59
|
+
this._debug("Target found. Running bindings for target: ", target)
|
60
|
+
|
61
|
+
const bindingCondition = this._getDatum('bindingCondition', source, target)
|
62
|
+
|
63
|
+
if (bindingCondition) {
|
64
|
+
this._debug(`Evaluating binding condition: '${bindingCondition}'`)
|
65
|
+
} else {
|
66
|
+
this._debug(`%cNo binding condition set. Evaluating as true. To add a condition set 'data-binding-condition="..."'`, "color: rgba(150,150,150,0.8);")
|
67
|
+
}
|
68
|
+
|
69
|
+
const conditionPassed = this._evaluate(
|
70
|
+
bindingCondition,
|
71
|
+
{
|
72
|
+
source,
|
73
|
+
target
|
74
|
+
}
|
75
|
+
)
|
76
|
+
|
77
|
+
if (conditionPassed) {
|
78
|
+
this._debug(`Condition evaluated to: `, conditionPassed)
|
79
|
+
} else {
|
80
|
+
this._debug(`Condition evaluated to: `, conditionPassed)
|
81
|
+
}
|
82
|
+
|
83
|
+
const bindingValue = this._getDatum('bindingValue', source, target)
|
84
|
+
|
85
|
+
if (bindingValue) {
|
86
|
+
this._debug(`Evaluating binding value: '${bindingValue}'`)
|
87
|
+
} else {
|
88
|
+
this._debug(`%cNo binding value set, evaluating as true. to set a value for the attribute / property on your target elements set 'data-binding-value="..."'`, "color: rgba(150,150,150,0.8);")
|
89
|
+
}
|
90
|
+
|
91
|
+
const value = this._evaluate(
|
92
|
+
bindingValue,
|
93
|
+
{
|
94
|
+
source,
|
95
|
+
target
|
96
|
+
}
|
97
|
+
)
|
98
|
+
|
99
|
+
this._debug(`Value evaluated to: '${value}'`)
|
100
|
+
|
101
|
+
const bindingAttribute = this._getDatum(
|
102
|
+
'bindingAttribute',
|
103
|
+
source,
|
104
|
+
target
|
105
|
+
)
|
106
|
+
|
107
|
+
if (!bindingAttribute) {
|
108
|
+
this._debug(`%cNo binding attribute set. To add attributes to your target element set 'data-binding-attribute="..."'`, "color: rgba(150,150,150,0.8);")
|
109
|
+
}
|
110
|
+
|
111
|
+
if (bindingAttribute) {
|
112
|
+
for (const attribute of bindingAttribute.split(' ')) {
|
113
|
+
if (conditionPassed) {
|
114
|
+
this._debug(`Condition passed so setting attribute '${attribute}' to '${value}'`)
|
115
|
+
target.setAttribute(attribute, value)
|
116
|
+
} else {
|
117
|
+
this._debug(`Condition failed so removing attribute '${attribute}'`)
|
118
|
+
target.removeAttribute(attribute)
|
119
|
+
}
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
const bindingProperty = this._getDatum(
|
124
|
+
'bindingProperty',
|
125
|
+
source,
|
126
|
+
target
|
127
|
+
)
|
128
|
+
|
129
|
+
if (!bindingProperty) {
|
130
|
+
this._debug(`%cNo binding property set. To add properties to your target element set 'data-binding-property="..."'`, "color: rgba(150,150,150,0.8);")
|
131
|
+
}
|
132
|
+
|
133
|
+
if (bindingProperty) {
|
134
|
+
for (const prop of bindingProperty.split(' ')) {
|
135
|
+
const propertyValue = conditionPassed ? value : ''
|
136
|
+
if (target[prop] != propertyValue) { target.dataset.hasChanged = true }
|
137
|
+
|
138
|
+
if (conditionPassed) {
|
139
|
+
this._debug(`Condition passed so setting property '${prop}' from ${target[prop]} to '${value}'`)
|
140
|
+
} else {
|
141
|
+
this._debug(`Condition failed so setting property '${prop}' from ${target[prop]} to '' (empty string)`)
|
142
|
+
}
|
143
|
+
|
144
|
+
target[prop] = propertyValue
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
const bindingClass = this._getDatum(
|
149
|
+
'bindingClass',
|
150
|
+
source,
|
151
|
+
target
|
152
|
+
)
|
153
|
+
|
154
|
+
if (!bindingClass) {
|
155
|
+
this._debug(`%cNo binding class set. To add classes to your target element set 'data-binding-class="..."'`, "color: rgba(150,150,150,0.8);")
|
156
|
+
}
|
157
|
+
|
158
|
+
if (bindingClass) {
|
159
|
+
for (const klass of bindingClass.split(' ')) {
|
160
|
+
if (conditionPassed) {
|
161
|
+
this._debug(`Condition passed so adding class '${klass}'`)
|
162
|
+
target.classList.add(klass)
|
163
|
+
} else {
|
164
|
+
this._debug(`Condition failed so removing class '${klass}'`)
|
165
|
+
target.classList.remove(klass)
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
const bindingEvent = this._getDatum('bindingEvent', source, target)
|
171
|
+
|
172
|
+
if (!bindingEvent) {
|
173
|
+
this._debug(`%cNo binding event set. To dispatch events on property change to your target element set 'data-binding-event="..."'`, "color: rgba(150,150,150,0.8);")
|
174
|
+
}
|
175
|
+
|
176
|
+
if (bindingEvent) {
|
177
|
+
for (const event of bindingEvent.split(' ')) {
|
178
|
+
if (target.dataset.hasChanged) {
|
179
|
+
this._debug(`Target has changed so dispatching event ${event}`)
|
180
|
+
target.dispatchEvent(new Event(event, { cancelable: true, bubbles: true }))
|
181
|
+
delete target.dataset.hasChanged
|
182
|
+
} else {
|
183
|
+
this._debug(`No changes to target properties so not dispatching '${event}'`)
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
if (this.debugMode) console.groupEnd()
|
189
|
+
}
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
/**
|
194
|
+
* @private
|
195
|
+
* @param {String} name - the name of the binding reference
|
196
|
+
*/
|
197
|
+
_bindingElements(name) {
|
198
|
+
return this.element.querySelectorAll(`[data-binding-ref="${name}"]`)
|
199
|
+
}
|
200
|
+
|
201
|
+
/**
|
202
|
+
* @private
|
203
|
+
* @param {String} attribute - the attribute to fetch from the source / target dataaset
|
204
|
+
* @param {String} source - The source element to get the attribute from, only loads if target doesnt have it
|
205
|
+
* @param {String} target - The target element to get the attribute from, has precedence over the source
|
206
|
+
*/
|
207
|
+
_getDatum(attribute, source, target) {
|
208
|
+
return target.dataset[attribute] || source.dataset[attribute]
|
209
|
+
}
|
210
|
+
|
211
|
+
/**
|
212
|
+
* @private
|
213
|
+
* @param {String} expression - expression to safe eval
|
214
|
+
* @param {Object} variables - variables to be present when evaluating the given expression
|
215
|
+
*/
|
216
|
+
_evaluate(expression, variables = {}) {
|
217
|
+
if (!expression) return true
|
218
|
+
return new Function(
|
219
|
+
Object.keys(variables).map((v) => `$${v}`),
|
220
|
+
`return ${expression.trim()}`
|
221
|
+
)(...Object.values(variables))
|
222
|
+
}
|
223
|
+
|
224
|
+
/**
|
225
|
+
* @private
|
226
|
+
* @param {String} expression - expression to safe eval
|
227
|
+
* @param {Object} variables - variables to be present when evaluating the given expression
|
228
|
+
*/
|
229
|
+
_debug(...args) {
|
230
|
+
if (this.debugMode) {
|
231
|
+
console.log(...args)
|
232
|
+
}
|
233
|
+
}
|
234
|
+
}
|