spina 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of spina might be problematic. Click here for more details.

Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/spina/application.js +1 -1
  3. data/app/assets/javascripts/spina/controllers/application.js +11 -0
  4. data/app/assets/javascripts/spina/controllers/embed_controller.js +16 -0
  5. data/app/assets/javascripts/spina/controllers/embed_tag_controller.js +16 -0
  6. data/app/assets/javascripts/spina/controllers/index.js +5 -0
  7. data/app/assets/javascripts/spina/controllers/trix_controller.js +12 -2
  8. data/app/assets/stylesheets/spina/_tailwind.css +18 -5
  9. data/app/assets/stylesheets/spina/tailwind/custom.css +13 -5
  10. data/app/components/spina/forms/trix_toolbar_component.html.erb +26 -15
  11. data/app/controllers/spina/admin/embeds_controller.rb +36 -0
  12. data/app/helpers/spina/admin/icons_helper.rb +2 -2
  13. data/app/helpers/spina/spina_helper.rb +3 -2
  14. data/app/models/spina/embeds/base.rb +7 -0
  15. data/app/models/spina/embeds/button.rb +13 -0
  16. data/app/models/spina/embeds/vimeo.rb +39 -0
  17. data/app/models/spina/embeds/youtube.rb +39 -0
  18. data/app/models/spina/page.rb +6 -0
  19. data/app/presenters/spina/content_presenter.rb +1 -1
  20. data/app/presenters/spina/rich_text_presenter.rb +45 -0
  21. data/app/views/spina/admin/embeds/new.html.erb +47 -0
  22. data/app/views/spina/admin/shared/_flash.html.erb +2 -2
  23. data/app/views/spina/embeds/buttons/_button.html.erb +3 -0
  24. data/app/views/spina/embeds/buttons/_button_fields.html.erb +14 -0
  25. data/app/views/spina/embeds/buttons/_trix_button.html.erb +3 -0
  26. data/app/views/spina/embeds/vimeos/_thumbnail.html.erb +6 -0
  27. data/app/views/spina/embeds/vimeos/_vimeo.html.erb +1 -0
  28. data/app/views/spina/embeds/vimeos/_vimeo_fields.html.erb +9 -0
  29. data/app/views/spina/embeds/youtubes/_thumbnail.html.erb +6 -0
  30. data/app/views/spina/embeds/youtubes/_youtube.html.erb +1 -0
  31. data/app/views/spina/embeds/youtubes/_youtube_fields.html.erb +9 -0
  32. data/config/initializers/importmap.rb +1 -1
  33. data/config/locales/en.yml +18 -0
  34. data/config/routes.rb +2 -0
  35. data/lib/generators/spina/templates/config/initializers/themes/default.rb +3 -0
  36. data/lib/generators/spina/templates/config/initializers/themes/demo.rb +3 -0
  37. data/lib/spina/embeddable.rb +48 -0
  38. data/lib/spina/embeds/trix_conversion.rb +41 -0
  39. data/lib/spina/embeds.rb +11 -0
  40. data/lib/spina/engine.rb +6 -4
  41. data/lib/spina/part.rb +10 -2
  42. data/lib/spina/railtie.rb +10 -0
  43. data/lib/spina/theme.rb +12 -1
  44. data/lib/spina/theme_reloader.rb +20 -0
  45. data/lib/spina/version.rb +1 -1
  46. data/lib/spina.rb +4 -0
  47. data/lib/tasks/spina_tasks.rake +1 -1
  48. metadata +36 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc21967f4a24ee99e20c76c80c6816cf921f4b0a270686049e8fb736f41c7b3d
4
- data.tar.gz: bd94d0854dd6d9e85569075ef965524f3d458820482fecb874fe2ff669ea28df
3
+ metadata.gz: 6eddb2bbef87fd0d66a6ca49f2234bf75a659a67ddf1ccbf95b5ccb0fc385332
4
+ data.tar.gz: 283ae2a27280cd16c66927ce8aa82210112372a8bc873d25519ee7cf3e3b8b1b
5
5
  SHA512:
6
- metadata.gz: 41393817fa4b6e939fa74f3c09d195edfa45b3142c4a30c99a21a6e85c1e0ca6c11516e1902170849ed487d9c47c064416516230685fac412b55af54bff37563
7
- data.tar.gz: b94ce566a3ce68541b957831193e7931738ae605b95595901f2dfa915159ae0702189127c67fe2badbd1550e3f491262e18fe4c11775e9712b484502d83088c6
6
+ metadata.gz: 41ba06425dd468e816f32031eef4eaa6bafef3defb437274c980d96d9e9dcc130dcb975942dfc0171bcd0883b07efb3a4037610c9c843f9b7ad936c3afb88e57
7
+ data.tar.gz: b973bea94d9d22268c076b7023de7dc617876a0064ac6cb47c2513499b5e082c0556a7f991573088c3a228d53c4ee4453f0f1c44756dbe4580c0d48c3be37617
@@ -1,3 +1,3 @@
1
1
  import "@hotwired/turbo-rails"
2
- import "@hotwired/stimulus-autoloader"
3
2
  import "libraries/trix"
3
+ import "controllers"
@@ -0,0 +1,11 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ // Configure Stimulus
6
+ application.warnings = true
7
+ application.debug = false
8
+ window.Stimulus = application
9
+
10
+ export { application }
11
+
@@ -0,0 +1,16 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static get targets() {
5
+ return [ "html" ]
6
+ }
7
+
8
+ insertEmbeddable(event) {
9
+ this.trixEditor.insertEmbeddable(event.detail.html)
10
+ }
11
+
12
+ get trixEditor() {
13
+ return document.getElementById(this.element.dataset.trixTarget).trix
14
+ }
15
+
16
+ }
@@ -0,0 +1,16 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+
5
+ connect() {
6
+ let event = new CustomEvent("embed-tag:embedded", this.eventOptions)
7
+ this.element.dispatchEvent(event)
8
+ }
9
+
10
+ get eventOptions() {
11
+ let clone = this.element.cloneNode(true)
12
+ clone.removeAttribute("data-controller")
13
+ return {bubbles: true, detail: {html: clone.outerHTML}}
14
+ }
15
+
16
+ }
@@ -0,0 +1,5 @@
1
+ import { application } from "controllers/application"
2
+
3
+ // Eager load all Stimulus controllers
4
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
5
+ eagerLoadControllersFrom("controllers", application)
@@ -5,11 +5,13 @@ export default class extends Controller {
5
5
  return [ "editor", "imageFields", "altField" ]
6
6
  }
7
7
 
8
- connect() {
8
+ connect() {
9
+ this.element[this.identifier] = this
10
+
9
11
  this.editorTarget.addEventListener("trix-selection-change", function(event) {
10
12
  if (this.mutableImageAttachment) {
11
13
  this.imageFieldsTarget.classList.remove("hidden")
12
- let position = this.mutableImageAttachment.querySelector("img").offsetTop + this.mutableImageAttachment.querySelector("img").offsetHeight - 16
14
+ let position = this.mutableImageAttachment.querySelector("img").offsetTop + this.mutableImageAttachment.querySelector("img").offsetHeight
13
15
  this.imageFieldsTarget.style.top = `${position}px`
14
16
  this.altFieldTarget.value = this.currentAltText
15
17
  } else {
@@ -18,6 +20,14 @@ export default class extends Controller {
18
20
  }.bind(this))
19
21
  }
20
22
 
23
+ insertEmbeddable(html) {
24
+ let embeddable = new Trix.Attachment({
25
+ content: html,
26
+ contentType: "application/vnd+spina.embed+html"})
27
+
28
+ this.editor.insertAttachment(embeddable)
29
+ }
30
+
21
31
  preventSubmission(event) {
22
32
  if (event.key === 'Enter') event.preventDefault() // Prevent form submit from alt text fields
23
33
  }
@@ -2342,18 +2342,20 @@ trix-editor {
2342
2342
  user-select: none
2343
2343
  }
2344
2344
  figure.attachment {
2345
+ margin: 0px;
2346
+ display: inline-block
2347
+ }
2348
+ figure.attachment[data-trix-content-type="Spina::Image"] {
2345
2349
  max-height: 150px;
2346
2350
  max-width: 200px;
2347
- margin: 0px;
2348
- display: inline-block;
2349
2351
  }
2350
- figure.attachment img {
2352
+ figure.attachment[data-trix-content-type="Spina::Image"] img {
2351
2353
  margin: 0px;
2352
2354
  border-radius: 0.375rem;
2353
2355
  -o-object-fit: contain;
2354
2356
  object-fit: contain;
2355
2357
  }
2356
- figure.attachment [data-label]:after {
2358
+ figure.attachment[data-trix-content-type="Spina::Image"] [data-label]:after {
2357
2359
  content: attr(data-label);
2358
2360
  margin-top: 0.25rem;
2359
2361
  display: flex;
@@ -2367,7 +2369,7 @@ trix-editor {
2367
2369
  --tw-text-opacity: 1;
2368
2370
  color: rgba(107, 114, 128, var(--tw-text-opacity));
2369
2371
  }
2370
- figure[data-trix-mutable].attachment img {
2372
+ figure[data-trix-mutable].attachment[data-trix-content-type="Spina::Image"] img {
2371
2373
  --tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
2372
2374
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2373
2375
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
@@ -2376,6 +2378,17 @@ trix-editor {
2376
2378
  --tw-ring-opacity: 1;
2377
2379
  --tw-ring-color: rgba(121, 122, 184, var(--tw-ring-opacity))
2378
2380
  }
2381
+ figure[data-trix-mutable][data-trix-content-type="application/vnd+spina.embed+html"].attachment > spina-embed {
2382
+ display: block;
2383
+ border-radius: 0.375rem;
2384
+ --tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
2385
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2386
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2387
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2388
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2389
+ --tw-ring-opacity: 1;
2390
+ --tw-ring-color: rgba(121, 122, 184, var(--tw-ring-opacity));
2391
+ }
2379
2392
  figure .attachment__caption {
2380
2393
  display: none
2381
2394
  }
@@ -154,25 +154,33 @@
154
154
  [data-trix-mutable]:not(.attachment__captain-editor) {
155
155
  @apply select-none
156
156
  }
157
-
157
+
158
158
  figure.attachment {
159
+ @apply m-0 inline-block
160
+ }
161
+
162
+ figure.attachment[data-trix-content-type="Spina::Image"] {
159
163
  max-height: 150px;
160
164
  max-width: 200px;
161
- @apply m-0 inline-block;
162
165
  }
163
166
 
164
- figure.attachment img {
167
+ figure.attachment[data-trix-content-type="Spina::Image"] img {
165
168
  @apply m-0 rounded-md object-contain;
166
169
  }
167
170
 
168
- figure.attachment [data-label]:after {
171
+ figure.attachment[data-trix-content-type="Spina::Image"] [data-label]:after {
169
172
  content: attr(data-label);
170
173
  @apply italic text-gray-500 h-8 flex items-center px-2 mt-1 text-sm;
171
174
  }
172
175
 
173
- figure[data-trix-mutable].attachment img {
176
+ figure[data-trix-mutable].attachment[data-trix-content-type="Spina::Image"] img {
174
177
  @apply shadow-lg ring ring-spina-light
175
178
  }
179
+
180
+ figure[data-trix-mutable][data-trix-content-type="application/vnd+spina.embed+html"].attachment > spina-embed {
181
+ @apply block;
182
+ @apply shadow-lg ring ring-spina-light rounded-md;
183
+ }
176
184
 
177
185
  figure .attachment__caption {
178
186
  @apply hidden
@@ -1,57 +1,68 @@
1
1
  <div class="relative sticky top-0 pt-4 bg-white trix-toolbar" id="<%= @trix_id %>">
2
2
  <div class="flex items-center flex-wrap" data-controller="reveal">
3
3
  <div class="flex items-center bg-gray-200 rounded overflow-hidden mb-3 mr-3">
4
- <button type="button" class="text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="bold" data-trix-key="b" title="${Trix.config.lang.bold}" tabindex="-1">
4
+ <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="bold" data-trix-key="b" title="${Trix.config.lang.bold}" tabindex="-1">
5
5
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z"/></svg>
6
6
  </button>
7
- <button type="button" class="text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="italic" data-trix-key="i" title="${Trix.config.lang.italic}" tabindex="-1">
7
+ <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="italic" data-trix-key="i" title="${Trix.config.lang.italic}" tabindex="-1">
8
8
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z"/></svg>
9
9
  </button>
10
- <button type="button" class="text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="href" data-trix-action="link" data-trix-key="k" title="<%=t 'spina.wysiwyg.link' %>" tabindex="-1" data-action="reveal#toggle">
10
+ <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="href" data-trix-action="link" data-trix-key="k" title="<%=t 'spina.wysiwyg.link' %>" tabindex="-1" data-action="reveal#toggle">
11
11
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z"/></svg>
12
12
  </button>
13
13
  </div>
14
14
 
15
15
  <div class="flex items-center bg-gray-200 rounded overflow-hidden mr-3 mb-3" data-trix-button-group="block-tools">
16
- <button type="button" class="text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="heading1" title="${Trix.config.lang.heading1}" tabindex="-1">
16
+ <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="heading1" title="${Trix.config.lang.heading1}" tabindex="-1">
17
17
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M304 96h-98.94A13.06 13.06 0 0 0 192 109.06v21.88A13.06 13.06 0 0 0 205.06 144H232v88H88v-88h26.94A13.06 13.06 0 0 0 128 130.94V112a16 16 0 0 0-16-16H16a16 16 0 0 0-16 16v18.94A13.06 13.06 0 0 0 13.06 144H40v224H13.06A13.06 13.06 0 0 0 0 381.06V400a16 16 0 0 0 16 16h98.94A13.06 13.06 0 0 0 128 402.94v-21.88A13.06 13.06 0 0 0 114.94 368H88v-88h144v88h-26.94A13.06 13.06 0 0 0 192 381.06V400a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-18.94A13.06 13.06 0 0 0 306.94 368H280V144h26.94A13.06 13.06 0 0 0 320 130.94V112a16 16 0 0 0-16-16zm256 272h-56V120a24 24 0 0 0-24-24h-24a24 24 0 0 0-21.44 13.26l-24 48A24 24 0 0 0 432 192h24v176h-56a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16z"/></svg>
18
18
  </button>
19
- <button type="button" class="text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="heading2" title="${Trix.config.lang.heading2}" tabindex="-1">
19
+ <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="heading2" title="${Trix.config.lang.heading2}" tabindex="-1">
20
20
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M304 96h-98.94A13.06 13.06 0 0 0 192 109.06v21.88A13.06 13.06 0 0 0 205.06 144H232v88H88v-88h26.94A13.06 13.06 0 0 0 128 130.94V112a16 16 0 0 0-16-16H16a16 16 0 0 0-16 16v18.94A13.06 13.06 0 0 0 13.06 144H40v224H13.06A13.06 13.06 0 0 0 0 381.06V400a16 16 0 0 0 16 16h98.94A13.06 13.06 0 0 0 128 402.94v-21.88A13.06 13.06 0 0 0 114.94 368H88v-88h144v88h-26.94A13.06 13.06 0 0 0 192 381.06V400a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-18.94A13.06 13.06 0 0 0 306.94 368H280V144h26.94A13.06 13.06 0 0 0 320 130.94V112a16 16 0 0 0-16-16zm244.14 272.13H410.82c8.52-60.35 146-79.28 146-179.31C556.8 134.17 515 96 455.05 96a114.71 114.71 0 0 0-97.92 55.05 11.81 11.81 0 0 0 3.58 15.95L382 181.23a11.89 11.89 0 0 0 16.27-3c13-18.23 31.58-31.35 53.72-31.35 29.57 0 49.43 18.08 49.43 45 0 67-149.45 84-149.45 195.49a137.14 137.14 0 0 0 1.39 18.42 12.18 12.18 0 0 0 11.8 10.21h183A11.85 11.85 0 0 0 560 404.18V380a11.85 11.85 0 0 0-11.86-11.87z"/></svg>
21
21
  </button>
22
- <button type="button" class="text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="heading3" title="${Trix.config.lang.heading3}" tabindex="-1">
22
+ <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="heading3" title="${Trix.config.lang.heading3}" tabindex="-1">
23
23
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M480.07 219.78l75.39-85.88a11.82 11.82 0 0 0 2.93-7.76v-18.32A11.89 11.89 0 0 0 546.44 96H379.87a11.88 11.88 0 0 0-11.94 11.82V132a11.88 11.88 0 0 0 11.94 11.83s102.44-.11 106-.25c-2.77 2.88-74.12 85.58-74.12 85.58a11.67 11.67 0 0 0-1.86 12.38l6.71 15.3a12.94 12.94 0 0 0 10.93 7.16h17.08c48.61 0 65.85 27.23 65.85 50.56 0 28.57-24.58 50.13-57.17 50.13-24 0-46.88-10.46-65.29-26.89a12 12 0 0 0-17.76 1.81l-16 22a11.73 11.73 0 0 0 1.4 15.41c24.6 23.34 60.62 39 99.38 39 64.2 0 109.86-46.22 109.86-103.17.01-51.1-36.73-84.17-84.81-93.07zM304 96h-98.94A13.06 13.06 0 0 0 192 109.06v21.88A13.06 13.06 0 0 0 205.06 144H232v88H88v-88h26.94A13.06 13.06 0 0 0 128 130.94V112a16 16 0 0 0-16-16H16a16 16 0 0 0-16 16v18.94A13.06 13.06 0 0 0 13.06 144H40v224H13.06A13.06 13.06 0 0 0 0 381.06V400a16 16 0 0 0 16 16h98.94A13.06 13.06 0 0 0 128 402.94v-21.88A13.06 13.06 0 0 0 114.94 368H88v-88h144v88h-26.94A13.06 13.06 0 0 0 192 381.06V400a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-18.94A13.06 13.06 0 0 0 306.94 368H280V144h26.94A13.06 13.06 0 0 0 320 130.94V112a16 16 0 0 0-16-16z"/></svg>
24
24
  </button>
25
- <button type="button" class="text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="heading4" title="${Trix.config.lang.heading4}" tabindex="-1">
25
+ <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="heading4" title="${Trix.config.lang.heading4}" tabindex="-1">
26
26
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M304 96h-98.94A13.06 13.06 0 0 0 192 109.06v21.88A13.06 13.06 0 0 0 205.06 144H232v88H88v-88h26.94A13.06 13.06 0 0 0 128 130.94V112a16 16 0 0 0-16-16H16a16 16 0 0 0-16 16v18.94A13.06 13.06 0 0 0 13.06 144H40v224H13.06A13.06 13.06 0 0 0 0 381.06V400a16 16 0 0 0 16 16h98.94A13.06 13.06 0 0 0 128 402.94v-21.88A13.06 13.06 0 0 0 114.94 368H88v-88h144v88h-26.94A13.06 13.06 0 0 0 192 381.06V400a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-18.94A13.06 13.06 0 0 0 306.94 368H280V144h26.94A13.06 13.06 0 0 0 320 130.94V112a16 16 0 0 0-16-16zm256 136h-16V112a16 16 0 0 0-16-16h-16a16 16 0 0 0-16 16v120h-96V112a16 16 0 0 0-16-16h-16a16 16 0 0 0-16 16v136a32 32 0 0 0 32 32h112v120a16 16 0 0 0 16 16h16a16 16 0 0 0 16-16V280h16a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16z"/></svg>
27
27
  </button>
28
28
  </div>
29
29
 
30
- <div class="flex items-center bg-gray-200 rounded overflow-hidden mr-3 mb-3">
31
- <%= link_to helpers.spina.admin_media_picker_path(target: "insert_#{@trix_id}"), class: "text-gray-700 w-9 h-9 flex items-center justify-center", data: {turbo_frame: "modal"} do %>
30
+ <div class="flex items-center bg-gray-200 rounded overflow-hidden mr-3 mb-3">
31
+
32
+ <%= link_to helpers.spina.admin_media_picker_path(target: "insert_#{@trix_id}"), class: "hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center", data: {turbo_frame: "modal"} do %>
32
33
  <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
33
34
  <path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd" />
34
35
  </svg>
35
36
  <% end %>
36
37
 
37
- <button type="button" class="text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="quote" title="${Trix.config.lang.quote}" tabindex="-1">
38
+ <% if helpers.current_theme.embeds.any? %>
39
+ <%= link_to helpers.spina.new_admin_embed_path(target: "insert_#{@trix_id}"), class: "hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center", data: {turbo_frame: "modal"} do %>
40
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
41
+ <path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM14 11a1 1 0 011 1v1h1a1 1 0 110 2h-1v1a1 1 0 11-2 0v-1h-1a1 1 0 110-2h1v-1a1 1 0 011-1z" />
42
+ </svg>
43
+ <% end %>
44
+ <% end %>
45
+ </div>
46
+
47
+ <div class="flex items-center bg-gray-200 rounded overflow-hidden mr-3 mb-3">
48
+ <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="quote" title="${Trix.config.lang.quote}" tabindex="-1">
38
49
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M464 32H336c-26.5 0-48 21.5-48 48v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48zm-288 0H48C21.5 32 0 53.5 0 80v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48z"/></svg>
39
50
  </button>
40
- <button type="button" class="text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="code" title="${Trix.config.lang.code}" tabindex="-1">
51
+ <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="code" title="${Trix.config.lang.code}" tabindex="-1">
41
52
  <svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
42
53
  <path fill-rule="evenodd" d="M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z" clip-rule="evenodd" />
43
54
  </svg>
44
55
  </button>
45
- <button type="button" class="trix-button trix-button--icon trix-button--icon-bullet-list text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="bullet" title="${Trix.config.lang.bullets}" tabindex="-1">
56
+ <button type="button" class="hover:bg-gray-300 trix-button trix-button--icon trix-button--icon-bullet-list text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="bullet" title="${Trix.config.lang.bullets}" tabindex="-1">
46
57
  <svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 512 512"><path d="M48 368a48 48 0 1 0 48 48 48 48 0 0 0-48-48zm0-160a48 48 0 1 0 48 48 48 48 0 0 0-48-48zm0-160a48 48 0 1 0 48 48 48 48 0 0 0-48-48zm448 24H176a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V88a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16z"/></svg>
47
58
  </button>
48
- <button type="button" class="trix-button trix-button--icon trix-button--icon-number-list text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="number" title="${Trix.config.lang.numbers}" tabindex="-1">
59
+ <button type="button" class="hover:bg-gray-300 trix-button trix-button--icon trix-button--icon-number-list text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="number" title="${Trix.config.lang.numbers}" tabindex="-1">
49
60
  <svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 512 512"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.84a154.82 154.82 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.3 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.73 6.13-3.2 11.72 2.62 15.94 7.71 4.69 20.39 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 392H176a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zm0-320H176a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V88a16 16 0 0 0-16-16zm0 160H176a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.9 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.33c3.28-10.29 48.33-18.68 48.33-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.45 18.75-4.38 5.59-3 10.84 2.79 15.37l8.58 6.88c5.61 4.56 11 2.47 16.13-2.44a13.4 13.4 0 0 1 9.45-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.1 320z"/></svg>
50
61
  </button>
51
- <button type="button" class="trix-button trix-button--icon trix-button--icon-decrease-nesting-level text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-action="decreaseNestingLevel" title="${Trix.config.lang.outdent}" tabindex="-1">
62
+ <button type="button" class="hover:bg-gray-300 trix-button trix-button--icon trix-button--icon-decrease-nesting-level text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-action="decreaseNestingLevel" title="${Trix.config.lang.outdent}" tabindex="-1">
52
63
  <svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 448 512"><path d="M100.69 363.29c10 10 27.31 2.93 27.31-11.31V160c0-14.32-17.33-21.31-27.31-11.31l-96 96a16 16 0 0 0 0 22.62zM432 424H16a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zm3.17-128H204.83A12.82 12.82 0 0 0 192 308.83v22.34A12.82 12.82 0 0 0 204.83 344h230.34A12.82 12.82 0 0 0 448 331.17v-22.34A12.82 12.82 0 0 0 435.17 296zM432 40H16A16 16 0 0 0 0 56v16a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16zm3.17 128H204.83A12.82 12.82 0 0 0 192 180.83v22.34A12.82 12.82 0 0 0 204.83 216h230.34A12.82 12.82 0 0 0 448 203.17v-22.34A12.82 12.82 0 0 0 435.17 168z"/></svg>
53
64
  </button>
54
- <button type="button" class="trix-button trix-button--icon trix-button--icon-increase-nesting-level text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-action="increaseNestingLevel" title="${Trix.config.lang.indent}" tabindex="-1">
65
+ <button type="button" class="hover:bg-gray-300 trix-button trix-button--icon trix-button--icon-increase-nesting-level text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-action="increaseNestingLevel" title="${Trix.config.lang.indent}" tabindex="-1">
55
66
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M432 424H16a16 16 0 0 0-16 16v16a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-16a16 16 0 0 0-16-16zM27.31 363.3l96-96a16 16 0 0 0 0-22.62l-96-96C17.27 138.66 0 145.78 0 160v192c0 14.31 17.33 21.3 27.31 11.3zM435.17 168H204.83A12.82 12.82 0 0 0 192 180.83v22.34A12.82 12.82 0 0 0 204.83 216h230.34A12.82 12.82 0 0 0 448 203.17v-22.34A12.82 12.82 0 0 0 435.17 168zM432 48H16A16 16 0 0 0 0 64v16a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16zm3.17 248H204.83A12.82 12.82 0 0 0 192 308.83v22.34A12.82 12.82 0 0 0 204.83 344h230.34A12.82 12.82 0 0 0 448 331.17v-22.34A12.82 12.82 0 0 0 435.17 296z"/></svg>
56
67
  </button>
57
68
  </div>
@@ -0,0 +1,36 @@
1
+ module Spina
2
+ module Admin
3
+ class EmbedsController < AdminController
4
+
5
+ def new
6
+ @embeddable = (Spina::Embeds.constantize(embed_type) || embeddables.first).new
7
+ end
8
+
9
+ def create
10
+ @embeddable = Spina::Embeds.constantize(embed_type).new(embed_params)
11
+
12
+ if @embeddable.valid?
13
+ render turbo_stream: turbo_stream.update(:trix_attachment_html, @embeddable.to_trix_attachment)
14
+ else
15
+ render :new, status: :unprocessable_entity
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def embeddables
22
+ @embeddables ||= current_theme.embeddables
23
+ end
24
+ helper_method :embeddables
25
+
26
+ def embed_type
27
+ params[:embed_type]
28
+ end
29
+
30
+ def embed_params
31
+ params.require(:embeddable).permit!
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -8,7 +8,7 @@ module Spina::Admin
8
8
 
9
9
  def heroicon(name, style: :outline, **options)
10
10
  file = read_file(Spina::Engine.root.join("app/assets/icons/heroicons", style.to_s, "#{name}.svg"))
11
-
11
+ return "" if file.nil?
12
12
  doc = Nokogiri::XML(file)
13
13
  svg = doc.root
14
14
  svg[:class] = options[:class]
@@ -18,7 +18,7 @@ module Spina::Admin
18
18
  private
19
19
 
20
20
  def read_file(path)
21
- File.exist?(path) || raise(FileNotFound, "File #{path} not found")
21
+ return nil unless File.exist?(path)
22
22
  File.read(path)
23
23
  end
24
24
 
@@ -1,10 +1,11 @@
1
1
  module Spina::SpinaHelper
2
2
 
3
- def spina_importmap_tags(entry_point = "application")
3
+ def spina_importmap_tags(entry_point = "application", shim: true)
4
4
  safe_join [
5
5
  javascript_inline_importmap_tag(Spina.config.importmap.to_json(resolver: self)),
6
6
  javascript_importmap_module_preload_tags(Spina.config.importmap),
7
- javascript_importmap_shim_tag,
7
+ (javascript_importmap_shim_nonce_configuration_tag if shim),
8
+ (javascript_importmap_shim_tag if shim),
8
9
  javascript_import_module_tag(entry_point)
9
10
  ], "\n"
10
11
  end
@@ -0,0 +1,7 @@
1
+ module Spina::Embeds
2
+ class Base
3
+ include ActiveModel::Model
4
+ include Spina::Embeddable
5
+ include Spina::Embeds::TrixConversion
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Spina::Embeds
2
+ class Button < Base
3
+ attributes :url, :label
4
+
5
+ heroicon "cursor-click"
6
+
7
+ validates :url, :label, presence: true
8
+
9
+ def to_trix_partial_path
10
+ "spina/embeds/buttons/trix_button"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ require 'net/http'
2
+
3
+ module Spina::Embeds
4
+ class Vimeo < Base
5
+ attributes :url
6
+
7
+ heroicon "video-camera"
8
+
9
+ REGEX = /(https?:\/\/)?(www.)?(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/
10
+
11
+ validates :url, presence: true, format: {with: REGEX}
12
+
13
+ def id
14
+ REGEX.match(url).try(:[], 5)
15
+ end
16
+
17
+ # Get title from Vimeo API (remote call)
18
+ def remote_title
19
+ get_vimeo_json&.dig(0, "title")
20
+ end
21
+
22
+ def to_trix_partial_path
23
+ "spina/embeds/vimeos/thumbnail"
24
+ end
25
+
26
+ private
27
+
28
+ def get_vimeo_json
29
+ uri = URI("https://vimeo.com/api/v2/video/#{id}.json")
30
+ response = Net::HTTP.get(uri)
31
+ begin
32
+ JSON.parse(response)
33
+ rescue
34
+ nil
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,39 @@
1
+ require 'net/http'
2
+
3
+ module Spina::Embeds
4
+ class Youtube < Base
5
+ attributes :url
6
+
7
+ heroicon "video-camera"
8
+
9
+ REGEX = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
10
+
11
+ validates :url, presence: true, format: {with: REGEX}
12
+
13
+ def id
14
+ REGEX.match(url).try(:[], 1)
15
+ end
16
+
17
+ # Get title from Youtube API (remote call)
18
+ def remote_title
19
+ get_youtube_json&.dig("title")
20
+ end
21
+
22
+ def to_trix_partial_path
23
+ "spina/embeds/youtubes/thumbnail"
24
+ end
25
+
26
+ private
27
+
28
+ def get_youtube_json
29
+ uri = URI("https://www.youtube.com/oembed?url=http://youtube.com/watch?v=#{id}&format=json")
30
+ response = Net::HTTP.get(uri)
31
+ begin
32
+ JSON.parse(response)
33
+ rescue
34
+ nil
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -26,6 +26,8 @@ module Spina
26
26
  scope :sorted, -> { order(:position) }
27
27
  scope :live, -> { active.where(draft: false) }
28
28
  scope :in_menu, -> { where(show_in_menu: true) }
29
+
30
+ before_create :set_default_position
29
31
 
30
32
  # Copy resource from parent
31
33
  before_save :set_resource_from_parent, if: -> { parent.present? }
@@ -102,6 +104,10 @@ module Spina
102
104
  end
103
105
 
104
106
  private
107
+
108
+ def set_default_position
109
+ self.position ||= self.class.maximum(:position).to_i.next
110
+ end
105
111
 
106
112
  def set_resource_from_parent
107
113
  self.resource_id = parent.resource_id
@@ -14,7 +14,7 @@ module Spina
14
14
 
15
15
  def html(name)
16
16
  html = find_part(name)&.content
17
- ActiveSupport::SafeBuffer.new(html.to_s)
17
+ RichTextPresenter.new(view_context, html)
18
18
  end
19
19
 
20
20
  def image_tag(image, variant_options = {}, options = {})
@@ -0,0 +1,45 @@
1
+ module Spina
2
+ class RichTextPresenter
3
+ attr_reader :html, :view_context
4
+
5
+ EMBED_CONTENT_TYPE = "application/vnd+spina.embed+html"
6
+
7
+ def initialize(view_context, html)
8
+ @view_context = view_context || Spina::Current.page&.view_context
9
+ @html = html
10
+ end
11
+
12
+ def to_s
13
+ ActiveSupport::SafeBuffer.new(render_embeds(html))
14
+ end
15
+
16
+ private
17
+
18
+ def embed_selector
19
+ "figure[data-trix-content-type=\"#{EMBED_CONTENT_TYPE}\"]"
20
+ end
21
+
22
+ def render_embeds(html)
23
+ doc = Nokogiri::HTML(html)
24
+ doc.css(embed_selector).each do |node|
25
+ node.replace render_embed(node.first_element_child)
26
+ end
27
+ doc.to_s
28
+ end
29
+
30
+ def render_embed(element)
31
+ embeddable = element_to_embeddable(element)
32
+ view_context.render(embeddable)
33
+ end
34
+
35
+ def element_to_embeddable(element)
36
+ embeddable = Spina::Embeds.constantize(element["data-embed-type"])
37
+ embeddable&.from_json(element["data-embed-attributes"]) || null_object
38
+ end
39
+
40
+ def null_object
41
+ {inline: ""}
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,47 @@
1
+ <%= render Spina::UserInterface::ModalComponent.new(size: "max-w-screen-md h-full md:h-96") do %>
2
+ <div class="h-full" data-controller="embed" data-trix-target="<%= params[:target] %>">
3
+ <%= turbo_frame_tag :trix_attachment_html, class: 'hidden', data: {embeddables_target: "html", action: "embed-tag:embedded->embed#insertEmbeddable embed-tag:embedded->modal#close"} %>
4
+
5
+ <%= turbo_frame_tag :embeddable_form do %>
6
+ <div class="flex h-full">
7
+ <div class="p-4 w-64 bg-gray-100 md:bg-opacity-50">
8
+ <div class="text-xs font-medium text-gray-600 mb-2">
9
+ <%=t "spina.embeds.embed_a_component" %>
10
+ </div>
11
+ <% embeddables.each do |embeddable| %>
12
+ <% if embeddable.name == @embeddable.class.name %>
13
+ <% classes = "font-medium w-full text-sm px-3 py-2 rounded-lg flex items-center text-gray-900 bg-spina-dark bg-opacity-20" %>
14
+ <% else %>
15
+ <% classes = "font-medium w-full text-sm px-3 py-2 rounded-lg flex items-center text-gray-600 hover:bg-gray-200 bg-opacity-100 hover:bg-gray-200" %>
16
+ <% end %>
17
+
18
+ <%= link_to spina.new_admin_embed_path(embed_type: embeddable.name), class: classes, data: {turbo_frame: :embeddable_form} do %>
19
+ <%= heroicon(embeddable.icon, style: :solid, class: 'w-5 h-5 mr-2 text-spina-light') %>
20
+ <%= embeddable.model_name.human %>
21
+ <% end %>
22
+ <% end %>
23
+ </div>
24
+
25
+ <div class="bg-white flex-1">
26
+ <%= form_with model: @embeddable, scope: :embeddable, url: spina.admin_embeds_path, class: 'h-full' do |f| %>
27
+ <%= hidden_field_tag :embed_type, @embeddable.class.name %>
28
+
29
+ <div class="h-full relative">
30
+ <div class="flex-1 p-5 h-full pb-20 overflow-scroll">
31
+ <%= render partial: @embeddable.to_fields_path, locals: {f: f} %>
32
+ </div>
33
+
34
+ <div class="w-full flex backdrop-filter backdrop-blur-lg absolute bottom-0 justify-end p-5 py-4 border-t border-gray-200">
35
+ <%= button_tag type: :submit, class: 'btn btn-primary' do %>
36
+ <%= heroicon('plus', style: :solid, class: 'w-6 h-6 -ml-2') %>
37
+ <%=t "spina.embeds.embed_component" %>
38
+ <% end %>
39
+ </div>
40
+ </div>
41
+ <% end %>
42
+ </div>
43
+ </div>
44
+ <% end %>
45
+
46
+ </div>
47
+ <% end %>
@@ -1,5 +1,5 @@
1
- <div class="fixed bottom-0 left-0 w-full flex justify-center z-50">
1
+ <div class="fixed bottom-0 left-0 w-full flex justify-center z-50 pointer-events-none">
2
2
  <% flash.each do |type, message| %>
3
3
  <%= render Spina::UserInterface::FlashMessageComponent.new(type: type, message: message) %>
4
4
  <% end %>
5
- </div>
5
+ </div>
@@ -0,0 +1,3 @@
1
+ <%= link_to button.url do %>
2
+ <%= button.label %>
3
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <div class="text-xl font-bold mb-3 text-gray-800">
2
+ Button
3
+ <div class="text-sm text-gray-500 font-normal">
4
+ A simple button that links somewhere
5
+ </div>
6
+ </div>
7
+
8
+ <%= render Spina::Forms::LabelComponent.new(f, :url) %>
9
+ <%= render Spina::Forms::TextFieldComponent.new(f, :url, autofocus: true) %>
10
+
11
+ <div class="mt-3">
12
+ <%= render Spina::Forms::LabelComponent.new(f, :label) %>
13
+ <%= render Spina::Forms::TextFieldComponent.new(f, :label) %>
14
+ </div>
@@ -0,0 +1,3 @@
1
+ <%= button_tag type: :button, class: "btn btn-default" do %>
2
+ <%= button.label %>
3
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <div class='border border-blue-600 text-blue-600 rounded-md flex items-center px-1 whitespace-nowrap'>
2
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
3
+ <path d="M2 6a2 2 0 012-2h6a2 2 0 012 2v8a2 2 0 01-2 2H4a2 2 0 01-2-2V6zM14.553 7.106A1 1 0 0014 8v4a1 1 0 00.553.894l2 1A1 1 0 0018 13V7a1 1 0 00-1.447-.894l-2 1z" />
4
+ </svg>
5
+ <%= vimeo.remote_title %>
6
+ </div>
@@ -0,0 +1 @@
1
+ <iframe src="https://player.vimeo.com/video/<%= vimeo.id %>" width="640" height="475" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
@@ -0,0 +1,9 @@
1
+ <div class="text-xl font-bold mb-3 text-gray-800">
2
+ Vimeo
3
+ <div class="text-sm text-gray-500 font-normal">
4
+ Embed a Vimeo video
5
+ </div>
6
+ </div>
7
+
8
+ <%= render Spina::Forms::LabelComponent.new(f, :url) %>
9
+ <%= render Spina::Forms::TextFieldComponent.new(f, :url, autofocus: true) %>
@@ -0,0 +1,6 @@
1
+ <div class='border border-red-600 text-red-600 rounded-md flex items-center px-1 whitespace-nowrap'>
2
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
3
+ <path d="M2 6a2 2 0 012-2h6a2 2 0 012 2v8a2 2 0 01-2 2H4a2 2 0 01-2-2V6zM14.553 7.106A1 1 0 0014 8v4a1 1 0 00.553.894l2 1A1 1 0 0018 13V7a1 1 0 00-1.447-.894l-2 1z" />
4
+ </svg>
5
+ <%= youtube.remote_title %>
6
+ </div>
@@ -0,0 +1 @@
1
+ <iframe id="ytplayer" type="text/html" width="640" height="360" src="https://www.youtube.com/embed/<%= youtube.id %>" frameborder="0"></iframe>
@@ -0,0 +1,9 @@
1
+ <div class="text-xl font-bold mb-3 text-gray-800">
2
+ Youtube
3
+ <div class="text-sm text-gray-500 font-normal">
4
+ Embed a Youtube video
5
+ </div>
6
+ </div>
7
+
8
+ <%= render Spina::Forms::LabelComponent.new(f, :url) %>
9
+ <%= render Spina::Forms::TextFieldComponent.new(f, :url, autofocus: true) %>
@@ -1,7 +1,7 @@
1
1
  Spina.config.importmap.draw do
2
2
  # Stimulus & Turbo
3
3
  pin "@hotwired/stimulus", to: "stimulus.js"
4
- pin "@hotwired/stimulus-autoloader", to: "stimulus-autoloader.js"
4
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
5
5
  pin "@hotwired/turbo-rails", to: "turbo.js"
6
6
 
7
7
  # Spina entrypoint
@@ -1,4 +1,10 @@
1
1
  en:
2
+ activemodel:
3
+ attributes:
4
+ spina/embeds/youtube:
5
+ url: youtube.com/watch?v=...
6
+ spina/embeds/vimeo:
7
+ url: vimeo.com/...
2
8
  activerecord:
3
9
  attributes:
4
10
  spina/account:
@@ -94,6 +100,15 @@ en:
94
100
  spina/user:
95
101
  one: User
96
102
  other: Users
103
+ helpers:
104
+ label:
105
+ spina/embeds/button:
106
+ url: URL
107
+ label: Button label
108
+ spina/embeds/youtube:
109
+ url: Youtube URL
110
+ spina/embeds/vimeo:
111
+ url: Vimeo URL
97
112
  spina:
98
113
  accounts:
99
114
  contact_details: Contact details
@@ -117,6 +132,9 @@ en:
117
132
  delete_confirmation: Are you sure you want to delete <strong>%{subject}</strong>?
118
133
  edit: Edit
119
134
  edit_website: Edit your website
135
+ embeds:
136
+ embed_a_component: Embed a component
137
+ embed_component: Embed component
120
138
  forgot_password:
121
139
  expired: Your password reset token has expired
122
140
  mail_subject: Reset your password
data/config/routes.rb CHANGED
@@ -80,6 +80,8 @@ Spina::Engine.routes.draw do
80
80
  resources :images
81
81
 
82
82
  resource :media_picker, controller: "media_picker", only: [:show]
83
+
84
+ resources :embeds, only: [:new, :create]
83
85
  end
84
86
 
85
87
  # Sitemap
@@ -59,4 +59,7 @@ Spina::Theme.register do |theme|
59
59
 
60
60
  # Plugins (optional)
61
61
  theme.plugins = []
62
+
63
+ # Embeds (optional)
64
+ theme.embeds = []
62
65
  end
@@ -69,4 +69,7 @@ Spina::Theme.register do |theme|
69
69
 
70
70
  # Plugins (optional)
71
71
  theme.plugins = ['reviews']
72
+
73
+ # Embeds (optional)
74
+ theme.embeds = []
72
75
  end
@@ -0,0 +1,48 @@
1
+ module Spina
2
+ module Embeddable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :embed_attributes, default: []
7
+ class_attribute :icon, default: nil
8
+ end
9
+
10
+ class_methods do
11
+ def from_json(json)
12
+ begin
13
+ attributes = JSON.parse(json)
14
+ attributes.transform_keys!(&:to_sym)
15
+ new(attributes)
16
+ rescue
17
+ Rails.logger.error "[#{self.class.name}] Couldn't parse JSON"
18
+ nil
19
+ end
20
+ end
21
+
22
+ def attributes(*names)
23
+ attr_accessor(*names.map(&:to_sym))
24
+ self.embed_attributes += names.map(&:to_sym)
25
+ end
26
+
27
+ # Give it an icon
28
+ def heroicon(name)
29
+ self.icon = name
30
+ end
31
+ end
32
+
33
+ def initialize(attributes = {})
34
+ attributes.slice(*self.class.embed_attributes).each do |key, value|
35
+ instance_variable_set("@#{key}", value)
36
+ end
37
+ end
38
+
39
+ def to_fields_path
40
+ "#{to_partial_path}_fields"
41
+ end
42
+
43
+ def to_trix_partial_path
44
+ to_partial_path
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,41 @@
1
+ module Spina
2
+ module Embeds
3
+ module TrixConversion
4
+ extend ActiveSupport::Concern
5
+
6
+ # Wrap rendered partial in an <embed> tag for Trix
7
+ def to_trix_attachment(content = trix_attachment_content)
8
+ wrap_with_embed_tag(content)
9
+ end
10
+
11
+ def wrap_with_embed_tag(html)
12
+ element = html_document.create_element("spina-embed", embed_tag_attributes)
13
+ element.inner_html = html
14
+ element.to_html
15
+ end
16
+
17
+ private
18
+
19
+ def embed_tag_attributes
20
+ spina_attributes.merge({"data-embed-attributes": embed_attributes_to_json})
21
+ end
22
+
23
+ def embed_attributes_to_json
24
+ JSON.generate Hash[self.class.embed_attributes.map{|a| [a.to_s, send(a)]}]
25
+ end
26
+
27
+ def spina_attributes
28
+ {"data-embed-type": self.class.name, "data-controller": "embed-tag"}
29
+ end
30
+
31
+ def trix_attachment_content
32
+ ApplicationController.render(partial: to_trix_partial_path, formats: :html, object: self, as: model_name.element)
33
+ end
34
+
35
+ def html_document
36
+ Nokogiri::HTML::Document.new.tap{|doc| doc.encoding = "UTF-8"}
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,11 @@
1
+ module Spina
2
+ module Embeds
3
+
4
+ def self.constantize(embeddable_type)
5
+ return nil if embeddable_type.blank?
6
+ name = embeddable_type.camelize.demodulize.to_s
7
+ "Spina::Embeds::#{name}".safe_constantize
8
+ end
9
+
10
+ end
11
+ end
data/lib/spina/engine.rb CHANGED
@@ -16,7 +16,7 @@ require 'browser'
16
16
  module Spina
17
17
  class Engine < ::Rails::Engine
18
18
  isolate_namespace Spina
19
-
19
+
20
20
  config.autoload_paths += %W( #{config.root}/lib )
21
21
 
22
22
  config.to_prepare do
@@ -26,8 +26,9 @@ module Spina
26
26
  require_dependency(decorator)
27
27
  end
28
28
  end
29
-
30
- # Register JSON part types for editing content
29
+ end
30
+
31
+ config.to_prepare do
31
32
  Spina::Part.register(
32
33
  Spina::Parts::Line,
33
34
  Spina::Parts::MultiLine,
@@ -38,6 +39,7 @@ module Spina
38
39
  Spina::Parts::Option,
39
40
  Spina::Parts::Attachment
40
41
  )
41
- end
42
+ end
43
+
42
44
  end
43
45
  end
data/lib/spina/part.rb CHANGED
@@ -8,12 +8,20 @@ module Spina
8
8
  end
9
9
 
10
10
  def register(*parts)
11
- parts.each{|part| all << part}
11
+ parts.each do |part|
12
+ unregister(part)
13
+ all << part
14
+ end
15
+ end
16
+
17
+ def unregister(part)
18
+ all.delete_if do |registered_part|
19
+ registered_part.name == part.name
20
+ end
12
21
  end
13
22
 
14
23
  end
15
24
 
16
-
17
25
  end
18
26
  end
19
27
 
data/lib/spina/railtie.rb CHANGED
@@ -5,6 +5,16 @@ module Spina
5
5
  app.config.assets.precompile += %w(spina/manifest)
6
6
  end
7
7
 
8
+ initializer "spina.theme_reloader" do |app|
9
+ reloader = ThemeReloader.new
10
+ reloader.execute
11
+
12
+ app.reloaders << reloader
13
+ app.reloader.to_run do
14
+ reloader.execute
15
+ end
16
+ end
17
+
8
18
  ActiveSupport.on_load(:action_controller) do
9
19
  ::ActionController::Base.send(:include, Spina::AdminSectionable)
10
20
  end
data/lib/spina/theme.rb CHANGED
@@ -1,13 +1,18 @@
1
1
  module Spina
2
2
  class Theme
3
3
 
4
- attr_accessor :name, :title, :parts, :page_parts, :structures, :view_templates, :layout_parts, :custom_pages, :plugins, :public_theme, :config, :navigations, :resources
4
+ attr_accessor :name, :title, :parts, :page_parts, :structures, :view_templates, :layout_parts, :custom_pages, :plugins, :public_theme, :config, :navigations, :resources, :embeds
5
5
 
6
6
  class << self
7
7
 
8
8
  def all
9
9
  ::Spina::THEMES
10
10
  end
11
+
12
+ def unregister(name)
13
+ theme = find_by_name(name)
14
+ all.delete(theme) if theme
15
+ end
11
16
 
12
17
  def find_by_name(name)
13
18
  all.find { |theme| theme.name == name }
@@ -17,6 +22,7 @@ module Spina
17
22
  theme = ::Spina::Theme.new
18
23
  yield theme
19
24
  raise 'Missing theme name' if theme.name.nil?
25
+ unregister(theme.name)
20
26
  if theme.plugins.nil?
21
27
  theme.plugins = ::Spina::Plugin.all.map { |plugin| plugin.name }
22
28
  end
@@ -33,8 +39,13 @@ module Spina
33
39
  @custom_pages = []
34
40
  @navigations = []
35
41
  @resources = []
42
+ @embeds = []
36
43
  @public_theme = false
37
44
  end
45
+
46
+ def embeddables
47
+ embeds.map{|embed| Embeds.constantize(embed)}
48
+ end
38
49
 
39
50
  def new_page_templates(resource: nil)
40
51
  page_collection = resource&.name || "main"
@@ -0,0 +1,20 @@
1
+ class Spina::ThemeReloader
2
+ delegate :execute_if_updated, :execute, :updated?, to: :updater
3
+
4
+ def reload!
5
+ theme_paths.each { |path| load path }
6
+ end
7
+
8
+ private
9
+
10
+ def updater
11
+ @updater ||= Rails.application.config.file_watcher.new(theme_paths) do
12
+ reload!
13
+ end
14
+ end
15
+
16
+ def theme_paths
17
+ Rails.root.glob("config/initializers/themes/*.rb")
18
+ end
19
+
20
+ end
data/lib/spina/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Spina
2
- VERSION = "2.4.0"
2
+ VERSION = "2.5.0"
3
3
  end
data/lib/spina.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'spina/engine'
2
2
  require 'spina/admin_sectionable'
3
3
  require 'spina/railtie'
4
+ require 'spina/theme_reloader'
4
5
  require 'spina/plugin'
5
6
  require 'spina/theme'
6
7
  require 'spina/tailwind_purger'
@@ -8,6 +9,9 @@ require 'spina/attr_json_spina_parts_model'
8
9
  require 'spina/attr_json_monkeypatch'
9
10
  require 'spina/authentication/sessions'
10
11
  require 'spina/authentication/basic'
12
+ require 'spina/embeddable'
13
+ require 'spina/embeds'
14
+ require 'spina/embeds/trix_conversion'
11
15
 
12
16
  module Spina
13
17
  include ActiveSupport::Configurable
@@ -25,7 +25,7 @@ namespace :spina do
25
25
  desc "Compile Tailwind.css for Spina"
26
26
  task :compile do
27
27
  Dir.chdir(File.join(__dir__, "../..")) do
28
- system "npx tailwindcss-cli@latest build -i ./app/assets/stylesheets/spina/tailwind/custom.css -o ./app/assets/stylesheets/spina/_tailwind.css -c ./app/assets/config/spina/tailwind.config.js"
28
+ system "npx tailwindcss@latest build -i ./app/assets/stylesheets/spina/tailwind/custom.css -o ./app/assets/stylesheets/spina/_tailwind.css -c ./app/assets/config/spina/tailwind.config.js"
29
29
  end
30
30
  end
31
31
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spina
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bram Jetten
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-09 00:00:00.000000000 Z
11
+ date: 2021-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -182,50 +182,44 @@ dependencies:
182
182
  name: importmap-rails
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
- - - '='
185
+ - - ">="
186
186
  - !ruby/object:Gem::Version
187
- version: 0.5.1
187
+ version: 0.7.6
188
188
  type: :runtime
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
- - - '='
192
+ - - ">="
193
193
  - !ruby/object:Gem::Version
194
- version: 0.5.1
194
+ version: 0.7.6
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: turbo-rails
197
197
  requirement: !ruby/object:Gem::Requirement
198
198
  requirements:
199
- - - "~>"
199
+ - - ">="
200
200
  - !ruby/object:Gem::Version
201
- version: 0.7.4
201
+ version: 0.8.0
202
202
  type: :runtime
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
- - - "~>"
206
+ - - ">="
207
207
  - !ruby/object:Gem::Version
208
- version: 0.7.4
208
+ version: 0.8.0
209
209
  - !ruby/object:Gem::Dependency
210
210
  name: stimulus-rails
211
211
  requirement: !ruby/object:Gem::Requirement
212
212
  requirements:
213
213
  - - ">="
214
214
  - !ruby/object:Gem::Version
215
- version: 0.3.8
216
- - - "<"
217
- - !ruby/object:Gem::Version
218
- version: 0.5.0
215
+ version: 0.7.0
219
216
  type: :runtime
220
217
  prerelease: false
221
218
  version_requirements: !ruby/object:Gem::Requirement
222
219
  requirements:
223
220
  - - ">="
224
221
  - !ruby/object:Gem::Version
225
- version: 0.3.8
226
- - - "<"
227
- - !ruby/object:Gem::Version
228
- version: 0.5.0
222
+ version: 0.7.0
229
223
  - !ruby/object:Gem::Dependency
230
224
  name: babosa
231
225
  requirement: !ruby/object:Gem::Requirement
@@ -821,17 +815,21 @@ files:
821
815
  - app/assets/images/spina/spina.png
822
816
  - app/assets/images/spina/spina.svg
823
817
  - app/assets/javascripts/spina/application.js
818
+ - app/assets/javascripts/spina/controllers/application.js
824
819
  - app/assets/javascripts/spina/controllers/attachment_picker_controller.js
825
820
  - app/assets/javascripts/spina/controllers/autofocus_controller.js
826
821
  - app/assets/javascripts/spina/controllers/button_controller.js
827
822
  - app/assets/javascripts/spina/controllers/confetti_controller.js
828
823
  - app/assets/javascripts/spina/controllers/confirm_controller.js
829
824
  - app/assets/javascripts/spina/controllers/delegate_click_controller.js
825
+ - app/assets/javascripts/spina/controllers/embed_controller.js
826
+ - app/assets/javascripts/spina/controllers/embed_tag_controller.js
830
827
  - app/assets/javascripts/spina/controllers/exists_controller.js
831
828
  - app/assets/javascripts/spina/controllers/form_controller.js
832
829
  - app/assets/javascripts/spina/controllers/hotkeys_controller.js
833
830
  - app/assets/javascripts/spina/controllers/image_collection_controller.js
834
831
  - app/assets/javascripts/spina/controllers/image_fade_in_controller.js
832
+ - app/assets/javascripts/spina/controllers/index.js
835
833
  - app/assets/javascripts/spina/controllers/infinite_scroll_controller.js
836
834
  - app/assets/javascripts/spina/controllers/loading_button_controller.js
837
835
  - app/assets/javascripts/spina/controllers/media_picker_controller.js
@@ -922,6 +920,7 @@ files:
922
920
  - app/controllers/spina/admin/accounts_controller.rb
923
921
  - app/controllers/spina/admin/admin_controller.rb
924
922
  - app/controllers/spina/admin/attachments_controller.rb
923
+ - app/controllers/spina/admin/embeds_controller.rb
925
924
  - app/controllers/spina/admin/images_controller.rb
926
925
  - app/controllers/spina/admin/layout_controller.rb
927
926
  - app/controllers/spina/admin/media_folders_controller.rb
@@ -963,6 +962,10 @@ files:
963
962
  - app/models/spina/application_record.rb
964
963
  - app/models/spina/attachment.rb
965
964
  - app/models/spina/current.rb
965
+ - app/models/spina/embeds/base.rb
966
+ - app/models/spina/embeds/button.rb
967
+ - app/models/spina/embeds/vimeo.rb
968
+ - app/models/spina/embeds/youtube.rb
966
969
  - app/models/spina/image.rb
967
970
  - app/models/spina/media_folder.rb
968
971
  - app/models/spina/navigation.rb
@@ -985,6 +988,7 @@ files:
985
988
  - app/models/spina/user.rb
986
989
  - app/presenters/spina/content_presenter.rb
987
990
  - app/presenters/spina/menu_presenter.rb
991
+ - app/presenters/spina/rich_text_presenter.rb
988
992
  - app/serializers/spina/api/base_serializer.rb
989
993
  - app/serializers/spina/api/image_serializer.rb
990
994
  - app/serializers/spina/api/navigation_serializer.rb
@@ -999,6 +1003,7 @@ files:
999
1003
  - app/views/spina/admin/attachments/edit.html.erb
1000
1004
  - app/views/spina/admin/attachments/index.html.erb
1001
1005
  - app/views/spina/admin/attachments/show.html.erb
1006
+ - app/views/spina/admin/embeds/new.html.erb
1002
1007
  - app/views/spina/admin/images/_image.html.erb
1003
1008
  - app/views/spina/admin/images/edit.html.erb
1004
1009
  - app/views/spina/admin/images/index.html.erb
@@ -1057,6 +1062,15 @@ files:
1057
1062
  - app/views/spina/admin/users/edit.html.erb
1058
1063
  - app/views/spina/admin/users/index.html.erb
1059
1064
  - app/views/spina/admin/users/new.html.erb
1065
+ - app/views/spina/embeds/buttons/_button.html.erb
1066
+ - app/views/spina/embeds/buttons/_button_fields.html.erb
1067
+ - app/views/spina/embeds/buttons/_trix_button.html.erb
1068
+ - app/views/spina/embeds/vimeos/_thumbnail.html.erb
1069
+ - app/views/spina/embeds/vimeos/_vimeo.html.erb
1070
+ - app/views/spina/embeds/vimeos/_vimeo_fields.html.erb
1071
+ - app/views/spina/embeds/youtubes/_thumbnail.html.erb
1072
+ - app/views/spina/embeds/youtubes/_youtube.html.erb
1073
+ - app/views/spina/embeds/youtubes/_youtube_fields.html.erb
1060
1074
  - app/views/spina/sitemaps/show.xml.builder
1061
1075
  - app/views/spina/user_mailer/forgot_password.html.erb
1062
1076
  - app/views/spina/user_mailer/forgot_password.text.erb
@@ -1115,12 +1129,16 @@ files:
1115
1129
  - lib/spina/attr_json_spina_parts_model.rb
1116
1130
  - lib/spina/authentication/basic.rb
1117
1131
  - lib/spina/authentication/sessions.rb
1132
+ - lib/spina/embeddable.rb
1133
+ - lib/spina/embeds.rb
1134
+ - lib/spina/embeds/trix_conversion.rb
1118
1135
  - lib/spina/engine.rb
1119
1136
  - lib/spina/part.rb
1120
1137
  - lib/spina/plugin.rb
1121
1138
  - lib/spina/railtie.rb
1122
1139
  - lib/spina/tailwind_purger.rb
1123
1140
  - lib/spina/theme.rb
1141
+ - lib/spina/theme_reloader.rb
1124
1142
  - lib/spina/version.rb
1125
1143
  - lib/tasks/spina_tasks.rake
1126
1144
  homepage: https://www.spinacms.com