satis 2.1.48 → 2.1.50

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20a33d2da55a21ccf04e5ecca7b4669423b15b0377e60db69c4cced1a38ac939
4
- data.tar.gz: fccc17ce2b1766f862d357ed3ca3abb40b546bee869f42ddbcdb717ecb7b29e7
3
+ metadata.gz: e8a393c04d2d3c296a40cbeff2d67c11d77035cacafd6861641422595a051ef1
4
+ data.tar.gz: eb3403bba306eb4e4953b31abda212636e0dc0f304917fc0ab1c6c533db50d98
5
5
  SHA512:
6
- metadata.gz: 5ca900e48520ae3bb301cea1f72f36d11c3fb5636a044c0b5c0f45c165fe5844e6c5e7a36eaaa21db36986cb2e0ced79669af6bccd2bdf3a33f76ef22b9677bd
7
- data.tar.gz: d4bdcb0054e81ee06a922b3f20bae18d940edfe0ad92e9d6e45bb1234268b5a7c29425b6da4812cdf3b1b3470ecda5056f6518daa25bc973e2551dd0d2ada323
6
+ metadata.gz: 69d33e8297502ab3dbe754f7cbca3d886c7cacd529d1356fda0acd6725bd77fe883db23e2cd5c51b2b4cc4deaf7d8ded771e086ddd60f5bdebca6fb5e2932369
7
+ data.tar.gz: d94edace53c39bb158ff07d39728901fdb849b14e2472c5a4983405ec4ad3d8501670d3a1798902e8056c7c37e46f228e0b26453579d8c626b1ec7e9dcf38767
@@ -1,5 +1,5 @@
1
1
  .attachments__group {
2
- @apply grid gap-6 mt-10 justify-start;
2
+ @apply grid gap-6 mt-0 justify-start;
3
3
  grid-template-columns: repeat(auto-fill, 200px);
4
4
  }
5
5
 
@@ -13,8 +13,7 @@
13
13
  }
14
14
 
15
15
  .attachments__attachment .attachments__button {
16
- @apply hidden absolute top-2.5 bg-white bg-opacity-80 p-1.5 rounded-full;
17
- @apply dark:bg-gray-700 dark:text-white;
16
+ @apply hidden;
18
17
  }
19
18
 
20
19
  .attachments__attachment .attachments__button:first-of-type {
@@ -26,7 +25,7 @@
26
25
  }
27
26
 
28
27
  .attachments__attachment:hover .attachments__button {
29
- @apply block;
28
+ @apply block w-7 h-7 justify-center items-center text-center bg-gray-100 dark:bg-gray-800 dark:text-white;
30
29
  }
31
30
 
32
31
  .attachments__attachment .attachments__filename {
@@ -74,22 +73,18 @@
74
73
  }
75
74
 
76
75
  .upload-btn {
77
- @apply dark:bg-gray-900 dark:text-white;
76
+ @apply dark:bg-gray-900 dark:text-white bg-white;
78
77
  align-items: center;
79
78
  padding: 10px 15px;
80
- background-color: #f0f0f0;
81
79
  border: 2px dashed #ccc;
82
80
  border-radius: 5px;
83
81
  cursor: pointer;
84
82
  transition: all 0.3s ease;
85
- font-family: Arial, sans-serif;
86
83
  color: #333;
87
84
  }
88
85
 
89
86
  .upload-btn:hover {
90
- @apply dark:bg-gray-900 dark:text-white dark:border-gray-700;
91
- background-color: #e0e0e0;
92
- border-color: #999;
87
+ @apply dark:bg-gray-900 dark:text-white dark:border-gray-700 border-gray-500 bg-white;
93
88
  }
94
89
 
95
90
  .upload-btn:focus {
@@ -103,12 +98,12 @@
103
98
  }
104
99
 
105
100
  .upload-btn .icon.upload {
106
- color: #4a90e2;
101
+ @apply text-primary-600;
107
102
  }
108
103
 
109
104
  .upload-btn .icon.uploading {
110
105
  display: none;
111
- color: #f39c12;
106
+ @apply text-primary-600;
112
107
  }
113
108
 
114
109
  .uploading .upload-btn .icon.upload {
@@ -129,8 +124,14 @@
129
124
  }
130
125
 
131
126
  .uploading .upload-btn {
132
- @apply dark:bg-gray-800 dark:border-gray-600 dark:text-white;
133
- background-color: #fcf8e3;
127
+ @apply dark:bg-gray-800 dark:border-gray-600 dark:text-white bg-white;
134
128
  border-color: #f39c12;
135
129
  cursor: not-allowed;
136
130
  }
131
+
132
+ .attachments__group .attachment-upload.upload-btn.attachments__attachment {
133
+ @apply bg-white dark:bg-gray-800 border-2 border-dashed hover:border-gray-500 border-gray-300 p-4 flex flex-col justify-center items-center text-center w-full min-h-[150px] box-border;
134
+ }
135
+ .attachments__group .attachment-upload.upload-btn.attachments__attachment.dark {
136
+ @apply hover:border-white;
137
+ }
@@ -1,26 +1,8 @@
1
- div.attachment-upload.upload-btn data-controller="attachment-upload" data-attachment-upload-url="#{Satis::Engine.routes.url_helpers.attachments_path(sgid: model_sgid, attribute: attribute)}" data-attachment-upload-param-name="attachments[]"data-attachment-upload-extra-data='{}' data-turbo="true" turbo-method="post"
2
- span.icon.upload
3
- i.fas.fa-upload
4
- span.icon.uploading
5
- i.fal.fa-circle-notch.fa-spin
6
- span.button-text Drag or click to attach files
7
-
8
-
9
-
10
- div.attachments__group
11
- - attachments = model_has_images ? model.images : model.attachments
12
- - attachments.each do |attachment|
13
- div.attachments__attachment(style= attachment_style(attachment))
14
- - unless attachment.representable?
15
- div.preview-text
16
- i.fas.fa-file(aria-hidden="true")
17
-
18
- = link_to Satis::Engine.routes.url_helpers.attachment_path(attachment, sgid: model_sgid, attribute: attribute), data: {turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this attachment?"}, class: 'attachments__button' do
19
- i.fas.fa-xmark
20
-
21
-
22
- = link_to Rails.application.routes.url_helpers.rails_blob_url(attachment, host: request.host + ":#{request.port}"), data: { turbo: true, turbolinks: false }, class: 'attachments__button', download: true do
23
- i.fas.fa-download
24
-
25
- span.attachments__filename
26
- = attachment.filename
1
+ turbo-frame id="#{model.id}_#{attribute}_attachments" target="_top" class="attachments__group"
2
+ div.attachment-upload.upload-btn.attachments__attachment data-controller="satis-attachment-upload" data-satis-attachment-upload-url-value=(model.persisted? ? Satis::Engine.routes.url_helpers.attachments_path(sgid: model_sgid, attribute: attribute) : false) data-satis-attachment-upload-parameter-name-value=(form&.field_name(attribute)||attribute) data-action="click->satis-attachment-upload#handleClick"
3
+ span.icon.upload
4
+ i.fas.fa-upload
5
+ span.icon.uploading
6
+ i.fal.fa-circle-notch.fa-spin
7
+ span.button-text data-satis-attachment-upload-target="button" Drag or click to attach files
8
+ = render partial: "satis/attachments/attachment", collection: model.send(attribute)
@@ -1,33 +1,18 @@
1
1
  module Satis
2
2
  module Attachments
3
3
  class Component < Satis::ApplicationComponent
4
- include Rails.application.routes.url_helpers
5
-
6
- attr_reader :model, :attribute, :attachments_options
4
+ attr_reader :model, :attribute, :attachments_options, :form
7
5
 
8
6
  def initialize(model, attribute, **options)
9
7
  super()
10
- @model = model
8
+ @form = options[:form]
9
+ @model = model || form.object
11
10
  @attribute = attribute
12
11
  @attachments_options = options
13
12
  end
14
13
 
15
- def model_has_images
16
- model.respond_to?(:images)
17
- end
18
-
19
- def attachment_style(attachment)
20
- if attachment.representable?
21
- url = attachment.representation(resize_to_limit: [200, 200]).processed.url
22
- "background-image: url(#{url})"
23
- else
24
- "background-color: f0f0f0"
25
- end
26
- end
27
-
28
-
29
14
  def model_sgid
30
- @model.to_sgid(expires_in: nil, for: 'satis_attachments')
15
+ @model.to_sgid(expires_in: nil, for: "satis_attachments")
31
16
  end
32
17
  end
33
18
  end
@@ -4,32 +4,31 @@ module Satis
4
4
  class AttachmentsController < ApplicationController
5
5
  before_action :set_objects
6
6
 
7
-
8
- def index
9
- @attachments = @model.public_send(@attachment_type)
10
- render json: @attachments
11
- end
12
-
13
7
  def create
14
- params[:attachments].each do |file|
15
- @model.public_send(@attachment_type).attach(file)
16
- end
17
- redirect_to request.referer || root_path, notice: "Attachment created successfully."
8
+ @model.public_send(@attribute).attach(params[:attachments])
9
+ @attachments = @model.public_send(@attribute).last(params[:attachments].size)
18
10
 
11
+ respond_to do |format|
12
+ format.html { redirect_to request.referer || root_path, notice: "Attachment created successfully." }
13
+ format.turbo_stream
14
+ end
19
15
  end
20
16
 
21
17
  def destroy
22
- attachment = @model.public_send(@attachment_type).find_by(id: params[:id])
23
- attachment&.purge
18
+ @attachment = @model.public_send(@attribute).find_by(id: params[:id])
19
+ @attachment&.purge
24
20
 
25
- redirect_to request.referer || root_path, notice: "Attachment deleted successfully."
21
+ respond_to do |format|
22
+ format.html { redirect_to request.referer || root_path, notice: "Attachment deleted successfully." }
23
+ format.turbo_stream
24
+ end
26
25
  end
27
26
 
28
27
  private
29
28
 
30
29
  def set_objects
31
- @attachment_type = params[:attribute] || 'attachments'
32
- @model = GlobalID::Locator.locate_signed(params[:sgid], for: 'satis_attachments')
30
+ @attribute = params[:attribute] || "attachments"
31
+ @model = GlobalID::Locator.locate_signed(params[:sgid], for: "satis_attachments")
33
32
  end
34
33
  end
35
- end
34
+ end
@@ -3,5 +3,13 @@ module Satis
3
3
  def sts
4
4
  @_satis_helpers_container ||= Satis::Helpers::Container.new(self)
5
5
  end
6
+
7
+ def method_missing(method, *args, **kwargs, &block)
8
+ if method.to_s.ends_with?("_url") || method.to_s.ends_with?("_path") && main_app.respond_to?(method)
9
+ main_app.send(method, *args, **kwargs, &block)
10
+ else
11
+ super
12
+ end
13
+ end
6
14
  end
7
15
  end
@@ -1,29 +1,48 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
+ import { post } from "@rails/request.js"
2
3
 
3
4
  export default class AttachmentUploadController extends Controller {
4
- static targets = ["input"]
5
+ static targets = ["button"]
6
+
7
+ static values = {
8
+ url: String,
9
+ parameterName: String
10
+ }
5
11
 
6
12
  connect() {
7
13
  this.createFileInput()
8
14
  this.addEventListeners()
9
15
  }
10
16
 
17
+ disconnect() {
18
+ this.fileInput.removeEventListener("change", this.handleChange.bind(this))
19
+ this.element.removeEventListener("dragover", this.handleDragOver.bind(this))
20
+ this.element.removeEventListener("dragleave", this.handleDragLeave.bind(this))
21
+ this.element.removeEventListener("dragenter", this.handleDragEnter.bind(this))
22
+ this.element.removeEventListener("drop", this.handleDrop.bind(this))
23
+ }
24
+
11
25
  createFileInput() {
26
+ // Mimic rails - this hiddenInput should prevent "old" files from being deleted
27
+ const hiddenInput = document.createElement("input")
28
+ hiddenInput.setAttribute("name", `${this.parameterNameValue}[]`)
29
+ hiddenInput.setAttribute("type", "hidden")
30
+ hiddenInput.setAttribute("autocomplete", "off")
31
+
32
+ this.element.closest('form').appendChild(hiddenInput)
33
+
12
34
  const input = document.createElement("input")
13
- input.setAttribute("name", this.data.get("param-name") || "file")
35
+
36
+ input.setAttribute("name", `${this.parameterNameValue}[]`)
14
37
  input.setAttribute("type", "file")
15
38
  input.setAttribute("multiple", "multiple")
16
39
  input.style.display = "none"
17
- this.element.appendChild(input)
18
- this.fileInput = input
19
40
 
20
- if (!this.data.has("param-name")) {
21
- console.warn(this.element, "has no data-upload-param attribute, uploads may not work")
22
- }
41
+ this.element.closest('form').appendChild(input)
42
+ this.fileInput = input
23
43
  }
24
44
 
25
45
  addEventListeners() {
26
- this.element.addEventListener("click", this.handleClick.bind(this))
27
46
  this.fileInput.addEventListener("change", this.handleChange.bind(this))
28
47
  this.element.addEventListener("dragover", this.handleDragOver.bind(this))
29
48
  this.element.addEventListener("dragleave", this.handleDragLeave.bind(this))
@@ -32,12 +51,15 @@ export default class AttachmentUploadController extends Controller {
32
51
  }
33
52
 
34
53
  handleClick(event) {
35
- event.preventDefault()
36
54
  this.fileInput.click()
37
55
  }
38
56
 
39
57
  handleChange(event) {
40
- this.upload(event.target.files)
58
+ if (this.hasUrlValue) {
59
+ this.upload(event.target.files)
60
+ } else {
61
+ this.buttonTarget.innerHTML = `${event.target.files.length} files selected`
62
+ }
41
63
  }
42
64
 
43
65
  handleDragOver(event) {
@@ -59,7 +81,11 @@ export default class AttachmentUploadController extends Controller {
59
81
  event.preventDefault()
60
82
  this.element.classList.remove("dragging")
61
83
  if (event.dataTransfer.files.length > 0) {
62
- this.upload(event.dataTransfer.files)
84
+ if (this.hasUrlValue) {
85
+ this.upload(event.dataTransfer.files)
86
+ } else {
87
+ this.buttonTarget.innerHTML = `${event.dataTransfer.files.length} files selected`
88
+ }
63
89
  }
64
90
  }
65
91
 
@@ -68,41 +94,17 @@ export default class AttachmentUploadController extends Controller {
68
94
  if (files.length === 0) return
69
95
 
70
96
  let formData = new FormData()
71
- if (this.data.has("extra-data")) {
72
- for (let [key, value] of Object.entries(JSON.parse(this.data.get("extra-data")))) {
73
- formData.append(key, value)
74
- }
75
- }
76
97
 
77
98
  for (let i = 0; i < files.length; i++) {
78
- formData.append(this.data.get("param-name"), files[i])
99
+ formData.append("attachments", files[i])
79
100
  }
80
101
 
81
102
  this.element.classList.add("uploading")
82
103
 
83
- fetch(this.data.get("url"), {
84
- method: 'POST',
104
+ post(this.urlValue, {
85
105
  body: formData,
86
- headers: {
87
- 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content,
88
- 'Accept': 'text/html, application/json'
89
- },
90
- redirect: 'follow' // Important: follow redirects
91
- }).then((response) => {
92
- // Check if the response is a redirect
93
- if (response.type === 'opaqueredirect' || response.redirected) {
94
- window.location.href = response.url
95
- return
96
- }
97
-
98
- if (response.ok) {
99
- this.element.classList.remove("uploading")
100
- window.location.reload(true)
101
- } else {
102
- throw new Error(response.statusText)
103
- }
104
- }).catch((error) => {
105
- console.log(error)
106
+ responseKind: 'turbo-stream'
107
+ }).then(html => {
106
108
  this.element.classList.remove("uploading")
107
109
  })
108
110
  }
@@ -59,9 +59,6 @@ application.register("satis-fields-for", FieldsForController);
59
59
  import AttachmentUploadController from "satis/controllers/attachment_upload_controller";
60
60
  application.register("satis-attachment-upload", AttachmentUploadController);
61
61
 
62
- import AttachmentDeleteController from "satis/controllers/attachment_delete_controller";
63
- application.register("satis-attachment-delete", AttachmentDeleteController);
64
-
65
62
  import FormController from "satis/controllers/form_controller";
66
63
  application.register("satis-form", FormController);
67
64
 
@@ -0,0 +1,13 @@
1
+ div.attachments__attachment style=(attachment.representable? ? "background-image: url(#{attachment.representation(resize_to_limit: [200, 200]).processed.url})" : "background-color: f0f0f0") id="attachment_#{attachment.id}"
2
+ - unless attachment.representable?
3
+ div.preview-text
4
+ i.fas.fa-file(aria-hidden="true")
5
+
6
+ = link_to Satis::Engine.routes.url_helpers.attachment_path(attachment, sgid: attachment.record.to_sgid(expires_in: nil, for: "satis_attachments"), attribute: attachment.name), data: {turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this attachment?"}, class: 'attachments__button' do
7
+ i.fas.fa-trash
8
+
9
+ = link_to Rails.application.routes.url_helpers.rails_blob_url(attachment, host: request.host + ":#{request.port}"), data: { turbo: false, turbolinks: false }, class: 'attachments__button', download: attachment.filename do
10
+ i.fas.fa-download
11
+
12
+ span.attachments__filename
13
+ = attachment.filename
@@ -0,0 +1 @@
1
+ = turbo_stream.append "#{@attachments.first.record.id}_#{@attachments.first.name}_attachments", partial: "satis/attachments/attachment", collection: @attachments
@@ -0,0 +1 @@
1
+ = turbo_stream.remove "attachment_#{@attachment.id}"
@@ -81,7 +81,7 @@ module Satis
81
81
  # FIXME: Yuk - is it possible to detect when this should not be allowed?
82
82
  # Like checking for whether destroy is allowed on assocations?
83
83
  allow_actions = options.key?(:allow_actions) ? options[:allow_actions] : true
84
- show_actions = @object.respond_to?("#{name}_attributes=") && @object.send(name).respond_to?(:each) && template_object && allow_actions == true
84
+ show_actions = @object.respond_to?(:"#{name}_attributes=") && @object.send(name).respond_to?(:each) && template_object && allow_actions == true
85
85
 
86
86
  html_options = options[:html] || {}
87
87
 
@@ -156,6 +156,14 @@ module Satis
156
156
  hidden_input(method, options, &block)
157
157
  end
158
158
 
159
+ def attachments(method, options = {}, &block)
160
+ self.multipart = true
161
+ safe_join [
162
+ @template.render(Satis::Attachments::Component.new(object, method, form: self, **value_text_method_options(options),
163
+ &block))
164
+ ]
165
+ end
166
+
159
167
  # Non public
160
168
 
161
169
  def input_type_for(method, options)
@@ -181,7 +189,7 @@ module Satis
181
189
  def form_group(method, options = {}, &block)
182
190
  tag.div(class: "form-group form-group-#{method}", data: options.delete(:data)) do
183
191
  safe_join [
184
- block.call,
192
+ yield,
185
193
  hint_text(options[:hint]),
186
194
  error_text(method)
187
195
  ].compact
data/lib/satis/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Satis
2
- VERSION = "2.1.48"
2
+ VERSION = "2.1.50"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: satis
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.48
4
+ version: 2.1.50
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom de Grunt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-21 00:00:00.000000000 Z
11
+ date: 2025-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: browser
@@ -871,7 +871,6 @@ files:
871
871
  - app/javascript/satis/application.js
872
872
  - app/javascript/satis/controllers/application.js
873
873
  - app/javascript/satis/controllers/application_controller.js
874
- - app/javascript/satis/controllers/attachment_delete_controller.js
875
874
  - app/javascript/satis/controllers/attachment_upload_controller.js
876
875
  - app/javascript/satis/controllers/fields_for_controller.js
877
876
  - app/javascript/satis/controllers/file_controller.js
@@ -890,6 +889,9 @@ files:
890
889
  - app/mailers/satis/application_mailer.rb
891
890
  - app/models/satis/application_record.rb
892
891
  - app/models/satis/user_data.rb
892
+ - app/views/satis/attachments/_attachment.html.slim
893
+ - app/views/satis/attachments/create.turbo_stream.slim
894
+ - app/views/satis/attachments/destroy.turbo_stream.slim
893
895
  - app/views/satis/documentation/avatars/index.html.slim
894
896
  - app/views/satis/documentation/cards/index.html.slim
895
897
  - app/views/satis/documentation/editors/index.html.slim
@@ -1,48 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus"
2
- import { get } from '@rails/request.js'
3
-
4
- /***
5
- * Delete attachments controller
6
- *
7
- * Deletes an attachments
8
- */
9
- export default class AttachmentDeleteController extends Controller {
10
- connect() {}
11
-
12
- delete(event) {
13
- const self = this
14
-
15
- event.stopPropagation()
16
- event.preventDefault()
17
-
18
- const formData = new FormData()
19
- formData.append("_method", "DELETE")
20
-
21
- fetch(self.element.getAttribute("href"), {
22
- method: "POST",
23
- headers: {
24
- "X-CSRF-Token": document.querySelector("meta[name=csrf-token]").content,
25
- },
26
- body: formData,
27
- })
28
- .then(self.handleErrors)
29
- .then((response) => {
30
- window.location.reload(true);
31
- response.json().then(function (data) {
32
- let node = document.querySelector(data.selector)
33
- if (node) {
34
- node.innerHTML = data.html
35
- }
36
- })
37
- })
38
- .catch((error) => {
39
- console.log(error)
40
- })
41
- return false
42
- }
43
-
44
- handleErrors(response) {
45
- if (!response.ok) throw new Error(response.status)
46
- return response
47
- }
48
- }