diff --git a/app/assets/javascripts/spina/controllers/image_part_paste_controller.js b/app/assets/javascripts/spina/controllers/image_part_paste_controller.js new file mode 100644 index 000000000..ff5041467 --- /dev/null +++ b/app/assets/javascripts/spina/controllers/image_part_paste_controller.js @@ -0,0 +1,65 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + + connect() { + // console.log('Image Part Paste controller connected!') + } + + async paste() { + if (!navigator.clipboard?.read) { + alert("Clipboard paste is not available. This feature requires HTTPS and browser clipboard permission.") + return + } + + let clipboardItems + try { + clipboardItems = await navigator.clipboard.read() + } catch (error) { + console.error("Clipboard access denied:", error) + alert("Could not read clipboard. Please allow clipboard access in your browser when prompted.") + return + } + + for (const item of clipboardItems) { + const imageType = item.types.find(type => type.startsWith("image/")) + if (!imageType) continue + + const blob = await item.getType(imageType) + const extension = imageType.split("/")[1] || "png" + const file = new File([blob], `pasted-image.${extension}`, { type: imageType }) + + await this.upload(file) + return + } + } + + async upload(file) { + console.log("Auto-uploaded pasted image...") + const formData = new FormData() + formData.append("image[files][]", file) + formData.append("origin", "image-part") + + const token = document.querySelector('meta[name="csrf-token"]')?.content + let response + try { + response = await fetch(this.uploadUrl, { + method: "POST", + headers: { "X-CSRF-Token": token, "Accept": "application/json" }, + body: formData + }) + } catch (error) { + console.error("Upload failed:", error) + return + } + + if (!response.ok) return + + const data = await response.json() + this.element.dispatchEvent(new CustomEvent("media-picker:done", { detail: data, bubbles: true })) + } + + get uploadUrl() { + return this.element.dataset.imagePartPasteUploadUrl + } +} diff --git a/app/controllers/spina/admin/images_controller.rb b/app/controllers/spina/admin/images_controller.rb index b3fa67bae..b7b9c708b 100644 --- a/app/controllers/spina/admin/images_controller.rb +++ b/app/controllers/spina/admin/images_controller.rb @@ -44,6 +44,16 @@ def create .new(images: @images, trix_target_id: params[:trix_target_id]) .render_in(view_context) ) + elsif params[:origin] == 'image-part' + image = @images.first + return head :unprocessable_entity unless image + render json: { + imageId: image.id, + signedBlobId: image.file.blob.signed_id, + filename: image.file.filename.to_s, + thumbnail: helpers.thumbnail_url(image), + embeddedUrl: helpers.embedded_image_url(image) + } else render turbo_stream: turbo_stream.prepend("images", partial: "image", collection: @images) end diff --git a/app/views/spina/admin/parts/images/_form.html.erb b/app/views/spina/admin/parts/images/_form.html.erb index 6e409791c..568cbf636 100644 --- a/app/views/spina/admin/parts/images/_form.html.erb +++ b/app/views/spina/admin/parts/images/_form.html.erb @@ -1,4 +1,4 @@ -
+
@@ -8,11 +8,18 @@ <%= f.hidden_field :filename, data: {media_picker_target: "filename"} %> <%= f.hidden_field :image_id, data: {media_picker_target: "imageId"} %> - +
+ + +
<%= link_to spina.admin_media_picker_path(target: dom_id(f.object, f.object.object_id)), class: "block relative mt-1 w-full", data: {turbo_frame: "modal"} do %>