Add permissions initializer and missing image paste controller
This commit is contained in:
121
app/javascript/controllers/image_paste_controller.js
Normal file
121
app/javascript/controllers/image_paste_controller.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["input", "dropzone"]
|
||||
|
||||
connect() {
|
||||
// Listen for paste events on the dropzone
|
||||
this.dropzoneTarget.addEventListener("paste", this.handlePaste.bind(this))
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.dropzoneTarget.removeEventListener("paste", this.handlePaste.bind(this))
|
||||
}
|
||||
|
||||
handlePaste(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const clipboardData = e.clipboardData || e.originalEvent.clipboardData
|
||||
|
||||
// First, try to get image data
|
||||
for (let item of clipboardData.items) {
|
||||
if (item.type.indexOf("image") !== -1) {
|
||||
const blob = item.getAsFile()
|
||||
this.handleImageBlob(blob)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If no image found, check for SVG text
|
||||
const text = clipboardData.getData("text/plain")
|
||||
if (text && this.isSVG(text)) {
|
||||
this.handleSVGText(text)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isSVG(text) {
|
||||
// Check if the text looks like SVG code
|
||||
const trimmed = text.trim()
|
||||
return trimmed.startsWith("<svg") && trimmed.includes("</svg>")
|
||||
}
|
||||
|
||||
handleSVGText(svgText) {
|
||||
// Validate file size (2MB)
|
||||
const size = new Blob([svgText]).size
|
||||
if (size > 2 * 1024 * 1024) {
|
||||
alert("SVG code is too large (must be less than 2MB)")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a blob from the SVG text
|
||||
const blob = new Blob([svgText], { type: "image/svg+xml" })
|
||||
|
||||
// Create a File object
|
||||
const file = new File([blob], `pasted-svg-${Date.now()}.svg`, {
|
||||
type: "image/svg+xml"
|
||||
})
|
||||
|
||||
// Create a DataTransfer object to set files on the input
|
||||
const dataTransfer = new DataTransfer()
|
||||
dataTransfer.items.add(file)
|
||||
this.inputTarget.files = dataTransfer.files
|
||||
|
||||
// Trigger change event to update preview (file-drop controller will handle it)
|
||||
const event = new Event("change", { bubbles: true })
|
||||
this.inputTarget.dispatchEvent(event)
|
||||
|
||||
// Visual feedback
|
||||
this.dropzoneTarget.classList.add("border-green-500", "bg-green-50")
|
||||
setTimeout(() => {
|
||||
this.dropzoneTarget.classList.remove("border-green-500", "bg-green-50")
|
||||
}, 500)
|
||||
}
|
||||
|
||||
handleImageBlob(blob) {
|
||||
// Validate file type
|
||||
const validTypes = ["image/png", "image/jpg", "image/jpeg", "image/gif", "image/svg+xml"]
|
||||
if (!validTypes.includes(blob.type)) {
|
||||
alert("Please paste a PNG, JPG, GIF, or SVG image")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate file size (2MB)
|
||||
if (blob.size > 2 * 1024 * 1024) {
|
||||
alert("Image size must be less than 2MB")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a File object from the blob with a default name
|
||||
const file = new File([blob], `pasted-image-${Date.now()}.${this.getExtension(blob.type)}`, {
|
||||
type: blob.type
|
||||
})
|
||||
|
||||
// Create a DataTransfer object to set files on the input
|
||||
const dataTransfer = new DataTransfer()
|
||||
dataTransfer.items.add(file)
|
||||
this.inputTarget.files = dataTransfer.files
|
||||
|
||||
// Trigger change event to update preview (file-drop controller will handle it)
|
||||
const event = new Event("change", { bubbles: true })
|
||||
this.inputTarget.dispatchEvent(event)
|
||||
|
||||
// Visual feedback
|
||||
this.dropzoneTarget.classList.add("border-green-500", "bg-green-50")
|
||||
setTimeout(() => {
|
||||
this.dropzoneTarget.classList.remove("border-green-500", "bg-green-50")
|
||||
}, 500)
|
||||
}
|
||||
|
||||
getExtension(mimeType) {
|
||||
const extensions = {
|
||||
"image/png": "png",
|
||||
"image/jpeg": "jpg",
|
||||
"image/jpg": "jpg",
|
||||
"image/gif": "gif",
|
||||
"image/svg+xml": "svg"
|
||||
}
|
||||
return extensions[mimeType] || "png"
|
||||
}
|
||||
}
|
||||
19
config/initializers/permissions_policy.rb
Normal file
19
config/initializers/permissions_policy.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# Configure the Permissions-Policy header
|
||||
# See https://api.rubyonrails.org/classes/ActionDispatch/PermissionsPolicy.html
|
||||
|
||||
Rails.application.config.permissions_policy do |f|
|
||||
# Disable sensitive browser features for security
|
||||
f.camera :none
|
||||
f.gyroscope :none
|
||||
f.microphone :none
|
||||
f.payment :none
|
||||
f.usb :none
|
||||
f.magnetometer :none
|
||||
|
||||
# You can enable specific features as needed:
|
||||
# f.fullscreen :self
|
||||
# f.geolocation :self
|
||||
|
||||
# You can also allow specific origins:
|
||||
# f.payment :self, "https://secure.example.com"
|
||||
end
|
||||
Reference in New Issue
Block a user