spina 2.19.0 → 2.20.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.

Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/spina/manifest.js +2 -2
  3. data/app/assets/javascripts/spina/application.js +1 -1
  4. data/app/assets/javascripts/spina/controllers/confetti_controller.js +1 -1
  5. data/app/assets/javascripts/spina/controllers/data_binding_controller.js +234 -1
  6. data/app/assets/javascripts/spina/controllers/form_controller.js +1 -2
  7. data/app/assets/javascripts/spina/controllers/hotkeys_controller.js +1 -1
  8. data/app/assets/javascripts/spina/controllers/reveal_controller.js +424 -1
  9. data/app/assets/javascripts/spina/controllers/sortable_controller.js +1 -2
  10. data/app/assets/javascripts/spina/libraries/canvas-confetti.js +1 -1
  11. data/app/assets/javascripts/spina/libraries/hotkeys.js +1 -1
  12. data/app/assets/javascripts/spina/libraries/sortablejs.js +1 -1
  13. data/app/assets/javascripts/spina/libraries/trix.js +1 -1
  14. data/app/assets/stylesheets/spina/fonts-propshaft.css +114 -0
  15. data/app/helpers/spina/admin/pages_helper.rb +23 -4
  16. data/app/models/spina/embeds/youtube.rb +1 -1
  17. data/app/views/layouts/spina/admin/application.html.erb +9 -2
  18. data/config/locales/de.yml +2 -0
  19. data/db/migrate/18_change_default_spina_resources_slug.rb +9 -0
  20. data/lib/spina/engine.rb +0 -1
  21. data/lib/spina/railtie.rb +4 -1
  22. data/lib/spina/version.rb +1 -1
  23. metadata +11 -27
  24. data/app/assets/javascripts/spina/libraries/form-request-submit-polyfill.js +0 -1
  25. data/app/assets/javascripts/spina/libraries/form-request-submit-polyfill@2.0.0.js +0 -27
  26. data/app/assets/javascripts/spina/libraries/stimulus-data-bindings@1.3.2.js +0 -234
  27. data/app/assets/javascripts/spina/libraries/stimulus-reveal@1.4.2.js +0 -424
  28. /data/app/assets/stylesheets/spina/{fonts.css.erb → fonts-sprockets.css.erb} +0 -0
@@ -1 +1,424 @@
1
- //= require ../libraries/stimulus-reveal@1.4.2.js
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ /**
4
+ * Stimulus controller to toggle element visibility
5
+ * @extends Controller
6
+ */
7
+ export default class RevealController extends Controller {
8
+ static values = {
9
+ open: Boolean,
10
+ transitioning: Boolean,
11
+ targetSelector: String,
12
+ toggleKeys: String,
13
+ showKeys: String,
14
+ hideKeys: String,
15
+ away: Boolean,
16
+ debug: Boolean,
17
+ };
18
+
19
+ connect() {
20
+ this._initCloseKeypressListener();
21
+ this._initToggleKeypressListener();
22
+ this._initShowKeypressListener();
23
+ this._awayHandler = this._awayHandler.bind(this);
24
+ }
25
+
26
+ disconnect() {
27
+ this._teardown();
28
+ }
29
+
30
+ /**
31
+ * Shows elements connected to the controller.
32
+ * @param {Event} event - an event with a currentTarget DOMElement
33
+ */
34
+ show(event) {
35
+ if (this.openValue || this.transitioningValue) return;
36
+
37
+ this._init(event, true);
38
+ }
39
+
40
+ /**
41
+ * Hides elements connected to the controller.
42
+ * @param {Event} event - an event with a currentTarget DOMElement
43
+ */
44
+ hide(event) {
45
+ if (!this.openValue || this.transitioningValue) return;
46
+
47
+ this._init(event, false);
48
+ }
49
+
50
+ /**
51
+ * Toggles elements connected to the controller.
52
+ * @param {Event} event - an event with a currentTarget DOMElement
53
+ */
54
+ toggle(event) {
55
+ if (this.transitioningValue) return;
56
+
57
+ this._init(event, !this.openValue);
58
+ }
59
+
60
+ /**
61
+ * Stops event propagation of elements connected to the controller.
62
+ * @param {Event} event - an event with a currentTarget DOMElement
63
+ */
64
+ stop(event) {
65
+ event.stopPropagation();
66
+ }
67
+
68
+ // Private methods
69
+
70
+ /**
71
+ * @private
72
+ * @param {Event} event
73
+ * @param {Event} shouldOpen
74
+ */
75
+ async _init(event, shouldOpen) {
76
+ if (event && event.currentTarget && event.currentTarget.dataset) {
77
+ if ("revealPreventDefault" in event.currentTarget.dataset) {
78
+ event.preventDefault();
79
+ }
80
+ if ("revealStopPropagation" in event.currentTarget.dataset) {
81
+ event.stopPropagation();
82
+ }
83
+ }
84
+ // start stuff
85
+ const startSelector = `${this.selector}[data-${shouldOpen ? "enter" : "leave"
86
+ }-start]`;
87
+ const startPromises = this._didInitWithPromise(startSelector, shouldOpen);
88
+ await Promise.all(startPromises);
89
+
90
+ const defaultSelector = `${this.selector}:not([data-${shouldOpen ? "enter" : "leave"
91
+ }-start]):not([data-${shouldOpen ? "enter" : "leave"}-end])`;
92
+ const defaultPromises = this._didInitWithPromise(
93
+ defaultSelector,
94
+ shouldOpen
95
+ );
96
+ await Promise.all(defaultPromises);
97
+
98
+ // end stuff
99
+ const endSelector = `${this.selector}[data-${shouldOpen ? "enter" : "leave"
100
+ }-end]`;
101
+ const endPromises = this._didInitWithPromise(endSelector, shouldOpen);
102
+ await Promise.all(endPromises);
103
+ }
104
+
105
+ /**
106
+ * @private
107
+ */
108
+ _teardown() {
109
+ if (this.hasAwayValue) {
110
+ document.removeEventListener("click", this._awayHandler);
111
+ }
112
+ }
113
+
114
+ _didInitWithPromise(selector, shouldOpen) {
115
+ this._debug("selecting", selector, this._fetchElements(selector));
116
+ return this._fetchElements(selector).map((element) => {
117
+ return this._doInitTransition(element, shouldOpen);
118
+ });
119
+ }
120
+
121
+ /**
122
+ * @private
123
+ */
124
+ _initCloseKeypressListener() {
125
+ if (this.hasHideKeysValue) {
126
+ document.addEventListener("keydown", (event) => {
127
+ if (!this.openValue) return;
128
+ if (!this.hideKeysValue.split(",").includes(event.key.toLowerCase())) {
129
+ return;
130
+ }
131
+
132
+ event.stopPropagation();
133
+ this.toggle(event);
134
+ });
135
+ }
136
+ }
137
+
138
+ /**
139
+ * @private
140
+ */
141
+ _initToggleKeypressListener() {
142
+ if (this.hasToggleKeysValue) {
143
+ document.addEventListener("keydown", (event) => {
144
+ if (
145
+ !this.toggleKeysValue.split(",").includes(event.key.toLowerCase())
146
+ ) {
147
+ return;
148
+ }
149
+
150
+ event.stopPropagation();
151
+
152
+ this.toggle(event);
153
+ });
154
+ }
155
+ }
156
+
157
+ /**
158
+ * @private
159
+ */
160
+ _initShowKeypressListener() {
161
+ if (this.hasShowKeysValue) {
162
+ document.addEventListener("keydown", (event) => {
163
+ if (this.openValue) return;
164
+ if (!this.showKeysValue.split(",").includes(event.key.toLowerCase())) {
165
+ return;
166
+ }
167
+
168
+ event.stopPropagation();
169
+
170
+ this.toggle(event);
171
+ });
172
+ }
173
+ }
174
+
175
+ /**
176
+ * @private
177
+ */
178
+ _awayHandler(event) {
179
+ if (!this.element.contains(event.target)) {
180
+ document.removeEventListener("click", this._awayHandler);
181
+ this.hide(event);
182
+ }
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * @private
188
+ * @param {DOMElement} target
189
+ * @param {boolean} openState
190
+ */
191
+ _doInitTransition(target, openState) {
192
+ this._debug("init transition", `${openState ? "open" : "closed"}`, target);
193
+ this._debug(
194
+ "dispatching event",
195
+ `reveal:${openState ? "show" : "hide"}`,
196
+ target
197
+ );
198
+ target.dispatchEvent(
199
+ new Event(`reveal:${openState ? "show" : "hide"}`, {
200
+ bubbles: true,
201
+ cancelable: false,
202
+ })
203
+ );
204
+
205
+ return new Promise((resolve, reject) => {
206
+ if (
207
+ "transition" in target.dataset &&
208
+ this.element.offsetParent !== null
209
+ ) {
210
+ requestAnimationFrame(() => {
211
+ this._transitionSetup(target, openState);
212
+ const _didEndTransition = this._didEndTransition.bind(this);
213
+
214
+ target.addEventListener(
215
+ "transitionend",
216
+ function _didEndTransitionHandler() {
217
+ _didEndTransition(target, openState);
218
+ target.removeEventListener(
219
+ "transitionend",
220
+ _didEndTransitionHandler
221
+ );
222
+ resolve();
223
+ }
224
+ );
225
+
226
+ requestAnimationFrame(() => {
227
+ this._doStartTransition(target, openState);
228
+ });
229
+ });
230
+ } else {
231
+ if (openState) {
232
+ this._debug(
233
+ "force hidden - init",
234
+ `${openState ? "open" : "closed"}`,
235
+ target
236
+ );
237
+ target.hidden = !target.hidden;
238
+ }
239
+ this._doCompleteTransition(target, openState);
240
+ resolve();
241
+ }
242
+ });
243
+ }
244
+
245
+ /**
246
+ * @private
247
+ * @param {DOMElement} target
248
+ */
249
+ _doStartTransition(target, openState) {
250
+ this._debug("start transition", `${openState ? "open" : "closed"}`, target);
251
+ this.transitioningValue = true;
252
+ if (target.dataset.useTransitionClasses === "true") {
253
+ const transitionClasses = this._transitionClasses(
254
+ target,
255
+ this.transitionType
256
+ );
257
+ target.classList.add(...transitionClasses.end.split(" "));
258
+ target.classList.remove(...transitionClasses.start.split(" "));
259
+ } else {
260
+ const transitions = this._transitionDefaults(openState);
261
+ target.style.transformOrigin = transitions.origin;
262
+ target.style.transitionProperty = "opacity transform";
263
+ target.style.transitionDuration = `${transitions.duration / 1000}s`;
264
+ target.style.transitionTimingFunction = "cubic-bezier(0.4, 0.0, 0.2, 1)";
265
+
266
+ target.style.opacity = transitions.to.opacity;
267
+ target.style.transform = `scale(${transitions.to.scale / 100})`;
268
+ }
269
+ }
270
+
271
+ /**
272
+ * @private
273
+ * @param {DOMElement} target
274
+ * @param {boolean} openState
275
+ */
276
+ _didEndTransition(target, openState) {
277
+ this._debug("end transition", `${openState ? "open" : "closed"}`, target);
278
+ if (target.dataset.useTransitionClasses === "true") {
279
+ const transitionClasses = this._transitionClasses(
280
+ target,
281
+ this.transitionType
282
+ );
283
+ target.classList.remove(...transitionClasses.before.split(" "));
284
+ } else {
285
+ target.style.opacity = target.dataset.opacityCache;
286
+ target.style.transform = target.dataset.transformCache;
287
+ target.style.transformOrigin = target.dataset.transformOriginCache;
288
+ }
289
+ this._doCompleteTransition(target, openState);
290
+ }
291
+
292
+ /**
293
+ * @private
294
+ * @param {DOMElement} target
295
+ * @param {boolean} openState
296
+ */
297
+ _doCompleteTransition(target, openState) {
298
+ this._debug(
299
+ "complete transition",
300
+ `${openState ? "open" : "closed"}`,
301
+ target
302
+ );
303
+ this.transitioningValue = false;
304
+
305
+ if (!openState) {
306
+ this._debug(
307
+ "force hidden - complete",
308
+ `${openState ? "open" : "closed"}`,
309
+ target
310
+ );
311
+ target.hidden = !target.hidden;
312
+ }
313
+ this.openValue = openState;
314
+
315
+ this._debug(
316
+ "dispatching event",
317
+ `reveal:${openState ? "shown" : "hidden"}`,
318
+ target
319
+ );
320
+ target.dispatchEvent(
321
+ new Event(`reveal:${openState ? "shown" : "hidden"}`, {
322
+ bubbles: true,
323
+ cancelable: false,
324
+ })
325
+ );
326
+
327
+ if (this.hasAwayValue && openState) {
328
+ document.addEventListener("click", this._awayHandler);
329
+ }
330
+
331
+ this._debug("dispatching event", "reveal:complete", target);
332
+ target.dispatchEvent(
333
+ new Event("reveal:complete", { bubbles: true, cancelable: false })
334
+ );
335
+ }
336
+
337
+ /**
338
+ * @private
339
+ * @param {DOMElement} target
340
+ * @param {boolean} openState
341
+ */
342
+ _transitionSetup(target, openState) {
343
+ this.transitionType = openState ? "transitionEnter" : "transitionLeave";
344
+
345
+ if (this.transitionType in target.dataset) {
346
+ target.dataset.useTransitionClasses = true;
347
+ const transitionClasses = this._transitionClasses(
348
+ target,
349
+ this.transitionType
350
+ );
351
+ target.classList.add(...transitionClasses.before.split(" "));
352
+ target.classList.add(...transitionClasses.start.split(" "));
353
+ } else {
354
+ target.dataset.useTransitionClasses = false;
355
+ const transitions = this._transitionDefaults(openState);
356
+ target.dataset.opacityCache = target.style.opacity;
357
+ target.dataset.transformCache = target.style.transform;
358
+ target.dataset.transformOriginCache = target.style.transformOrigin;
359
+
360
+ target.style.opacity = transitions.from.opacity;
361
+ target.style.transform = `scale(${transitions.from.scale / 100})`;
362
+ }
363
+ if (openState) {
364
+ this._debug("opening with transition", target);
365
+ target.hidden = !target.hidden;
366
+ }
367
+ }
368
+
369
+ /**
370
+ * @private
371
+ * @param {boolean} openState
372
+ */
373
+ _transitionDefaults(openState) {
374
+ return {
375
+ duration: openState ? 200 : 150,
376
+ origin: "center",
377
+ from: {
378
+ opacity: openState ? 0 : 1,
379
+ scale: openState ? 95 : 100,
380
+ },
381
+ to: {
382
+ opacity: openState ? 1 : 0,
383
+ scale: openState ? 100 : 95,
384
+ },
385
+ };
386
+ }
387
+
388
+ /**
389
+ * @private
390
+ * @param {DOMElement} target
391
+ * @param {string} transitionType
392
+ */
393
+ _transitionClasses(target, transitionType) {
394
+ return {
395
+ before: target.dataset[transitionType],
396
+ start: target.dataset[`${transitionType}Start`],
397
+ end: target.dataset[`${transitionType}End`],
398
+ };
399
+ }
400
+
401
+ /**
402
+ * @private
403
+ * @param {String} selector
404
+ */
405
+ _fetchElements(selector) {
406
+ return [this.element, ...this.element.querySelectorAll(selector)].filter(
407
+ (el) => el.matches(selector)
408
+ );
409
+ }
410
+
411
+ /**
412
+ * @private
413
+ * @param {Array} args
414
+ */
415
+ _debug(...args) {
416
+ if (this.debugValue) console.log(...args);
417
+ }
418
+
419
+ get selector() {
420
+ return this.hasTargetSelectorValue
421
+ ? this.targetSelectorValue
422
+ : "[data-reveal]";
423
+ }
424
+ }
@@ -1,6 +1,5 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
2
  import Sortable from "libraries/sortablejs"
3
- import formRequestSubmitPolyfill from "libraries/form-request-submit-polyfill"
4
3
 
5
4
  export default class extends Controller {
6
5
  static get targets() {
@@ -36,4 +35,4 @@ export default class extends Controller {
36
35
  return this.sortable.toArray()
37
36
  }
38
37
 
39
- }
38
+ }
@@ -1 +1 @@
1
- //= require ./canvas-confetti@1.3.2.js
1
+ export { default } from "libraries/canvas-confetti@1.3.2"
@@ -1 +1 @@
1
- //= require ./hotkeys@3.8.7.js
1
+ export { default } from "libraries/hotkeys@3.8.7"
@@ -1 +1 @@
1
- //= require ./sortablejs@1.13.0.js
1
+ export { default } from "libraries/sortablejs@1.13.0"
@@ -1,4 +1,4 @@
1
- //= require ./trix@1.3.1.esm.js
1
+ import "libraries/trix@1.3.1.esm"
2
2
 
3
3
  // Extra headings
4
4
  Trix.config.blockAttributes.heading2 = {
@@ -0,0 +1,114 @@
1
+ @font-face {
2
+ font-family: "Metropolis";
3
+ font-weight: 400;
4
+ src: url('Metropolis-Regular.woff2') format("woff2");
5
+ }
6
+
7
+ @font-face {
8
+ font-family: "Metropolis";
9
+ font-style: italic;
10
+ src: url('Metropolis-RegularItalic.woff2') format("woff2");
11
+ }
12
+
13
+ @font-face {
14
+ font-family: "Metropolis";
15
+ font-weight: 100;
16
+ src: url('Metropolis-Thin.woff2') format("woff2");
17
+ }
18
+
19
+ @font-face {
20
+ font-family: "Metropolis";
21
+ font-weight: 100;
22
+ font-style: italic;
23
+ src: url('Metropolis-ThinItalic.woff2') format("woff2");
24
+ }
25
+
26
+ @font-face {
27
+ font-family: "Metropolis";
28
+ font-weight: 200;
29
+ src: url('Metropolis-ExtraLight.woff2') format("woff2");
30
+ }
31
+
32
+ @font-face {
33
+ font-family: "Metropolis";
34
+ font-weight: 200;
35
+ src: url('Metropolis-ExtraLightItalic.woff2') format("woff2");
36
+ }
37
+
38
+ @font-face {
39
+ font-family: "Metropolis";
40
+ font-weight: 300;
41
+ src: url('Metropolis-Light.woff2') format("woff2");
42
+ }
43
+
44
+ @font-face {
45
+ font-family: "Metropolis";
46
+ font-weight: 300;
47
+ font-style: italic;
48
+ src: url('Metropolis-LightItalic.woff2') format("woff2");
49
+ }
50
+
51
+ @font-face {
52
+ font-family: "Metropolis";
53
+ font-weight: 500;
54
+ src: url('Metropolis-Medium.woff2') format("woff2");
55
+ }
56
+
57
+ @font-face {
58
+ font-family: "Metropolis";
59
+ font-weight: 500;
60
+ font-style: italic;
61
+ src: url('Metropolis-MediumItalic.woff2') format("woff2");
62
+ }
63
+
64
+ @font-face {
65
+ font-family: "Metropolis";
66
+ font-weight: 600;
67
+ src: url('Metropolis-SemiBold.woff2') format("woff2");
68
+ }
69
+
70
+ @font-face {
71
+ font-family: "Metropolis";
72
+ font-weight: 600;
73
+ font-style: italic;
74
+ src: url('Metropolis-SemiBoldItalic.woff2') format("woff2");
75
+ }
76
+
77
+ @font-face {
78
+ font-family: "Metropolis";
79
+ font-weight: 700;
80
+ src: url('Metropolis-Bold.woff2') format("woff2");
81
+ }
82
+
83
+ @font-face {
84
+ font-family: "Metropolis";
85
+ font-weight: 700;
86
+ font-style: italic;
87
+ src: url('Metropolis-BoldItalic.woff2') format("woff2");
88
+ }
89
+
90
+ @font-face {
91
+ font-family: "Metropolis";
92
+ font-weight: 800;
93
+ src: url('Metropolis-ExtraBold.woff2') format("woff2");
94
+ }
95
+
96
+ @font-face {
97
+ font-family: "Metropolis";
98
+ font-weight: 800;
99
+ font-style: italic;
100
+ src: url('Metropolis-ExtraBoldItalic.woff2') format("woff2");
101
+ }
102
+
103
+ @font-face {
104
+ font-family: "Metropolis";
105
+ font-weight: 900;
106
+ src: url('Metropolis-Black.woff2') format("woff2");
107
+ }
108
+
109
+ @font-face {
110
+ font-family: "Metropolis";
111
+ font-weight: 900;
112
+ font-style: italic;
113
+ src: url('Metropolis-BlackItalic.woff2') format("woff2");
114
+ }
@@ -1,10 +1,10 @@
1
1
  module Spina::Admin
2
2
  module PagesHelper
3
3
  def asset_available?(path)
4
- if Rails.configuration.assets.compile
5
- Rails.application.precompiled_assets.include?(path)
6
- else
7
- Rails.application.assets_manifest.assets[path].present?
4
+ if defined?(Propshaft)
5
+ check_propshaft_asset(path)
6
+ elsif defined?(Sprockets)
7
+ check_sprockets_asset(path)
8
8
  end
9
9
  end
10
10
 
@@ -24,5 +24,24 @@ module Spina::Admin
24
24
  def option_label(part, value)
25
25
  t(["options", part.name, value].compact.join("."))
26
26
  end
27
+
28
+ private
29
+
30
+ def check_propshaft_asset(path)
31
+ if Rails.configuration.assets.compile
32
+ Rails.application.assets.load_path.find(path).present? rescue false
33
+ else
34
+ Rails.application.assets.asset_for(path).present? rescue false
35
+ end
36
+ end
37
+
38
+ def check_sprockets_asset(path)
39
+ if Rails.configuration.assets.compile
40
+ Rails.application.precompiled_assets.include?(path)
41
+ else
42
+ Rails.application.assets_manifest.assets[path].present?
43
+ end
44
+ end
45
+
27
46
  end
28
47
  end
@@ -6,7 +6,7 @@ module Spina::Embeds
6
6
 
7
7
  heroicon "video-camera"
8
8
 
9
- REGEX = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
9
+ REGEX = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|shorts\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
10
10
 
11
11
  validates :url, presence: true, format: {with: REGEX}
12
12
 
@@ -10,8 +10,15 @@
10
10
 
11
11
  <title><%= Spina.config.backend_title %></title>
12
12
 
13
+ <!-- Fonts -->
14
+ <% if defined?(Propshaft) %>
15
+ <%= stylesheet_link_tag "spina/fonts-propshaft", data: {turbo_track: "reload"} %>
16
+ <% elsif defined?(Sprockets) %>
17
+ <%= stylesheet_link_tag "spina/fonts-sprockets", data: {turbo_track: "reload"} %>
18
+ <% end %>
19
+
13
20
  <!-- Stylesheets -->
14
- <%= stylesheet_link_tag "spina/tailwind", "spina/fonts", "spina/animate", "data-turbo-track": "reload" %>
21
+ <%= stylesheet_link_tag "spina/tailwind", "spina/animate", "data-turbo-track": "reload" %>
15
22
 
16
23
  <!-- Spina's importmap -->
17
24
  <%= spina_importmap_tags %>
@@ -25,4 +32,4 @@
25
32
  <%= render "spina/admin/shared/version" %>
26
33
  <%= content_for?(:body) ? yield(:body) : yield %>
27
34
  </body>
28
- </html>
35
+ </html>
@@ -241,6 +241,8 @@ de:
241
241
  wrong_username_or_password: E-Mail oder Passwort falsch
242
242
  options:
243
243
  choose_option: Option auswählen
244
+ page_links:
245
+ text_placeholder: "Optionaler Text für Link"
244
246
  pages:
245
247
  add_page_to: "Seite hinzufügen zu:"
246
248
  add_translation: Übersetzung für %{language} hinzufügen
@@ -0,0 +1,9 @@
1
+ class ChangeDefaultSpinaResourcesSlug < ActiveRecord::Migration[7.0]
2
+ def up
3
+ change_column :spina_resources, :slug, :jsonb, default: {}
4
+ end
5
+
6
+ def down
7
+ change_column :spina_resources, :slug, :jsonb, default: nil
8
+ end
9
+ end
data/lib/spina/engine.rb CHANGED
@@ -11,7 +11,6 @@ require "attr_json"
11
11
  require "view_component"
12
12
  require "jsonapi/serializer"
13
13
  require "browser"
14
- require "sprockets/railtie"
15
14
 
16
15
  module Spina
17
16
  class Engine < ::Rails::Engine
data/lib/spina/railtie.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  module Spina
2
2
  class Railtie < Rails::Railtie
3
3
  initializer "spina.assets.precompile" do |app|
4
- app.config.assets.precompile += %w[spina/manifest]
4
+ if defined?(Sprockets)
5
+ # Sprockets configuration
6
+ app.config.assets.precompile += %w[spina/manifest]
7
+ end
5
8
  end
6
9
 
7
10
  initializer "spina.theme_reloader" do |app|
data/lib/spina/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Spina
2
- VERSION = "2.19.0"
2
+ VERSION = "2.20.0"
3
3
  end