diff --git a/app/controllers/better_together/content/blocks_controller.rb b/app/controllers/better_together/content/blocks_controller.rb index 3ab90a193..0c5d65ed9 100644 --- a/app/controllers/better_together/content/blocks_controller.rb +++ b/app/controllers/better_together/content/blocks_controller.rb @@ -17,7 +17,8 @@ def index end def create - @block = resource_class.new(block_params) + @block = resource_class.new(block_params.except(:media_signed_id)) + attach_signed_media(@block) if @block.save redirect_to content_block_path(@block), @@ -27,11 +28,13 @@ def create end end - def update # rubocop:todo Metrics/MethodLength + def update + @block.assign_attributes(block_params.except(:media_signed_id)) + attach_signed_media(@block) + respond_to do |format| - if @block.update(block_params) - redirect_to edit_content_block_path(@block), - notice: t('flash.generic.updated', resource: t('resources.block')) + if @block.save + redirect_to edit_content_block_path(@block), notice: t('flash.generic.updated', resource: t('resources.block')) else format.turbo_stream do render turbo_stream: turbo_stream.replace(helpers.dom_id(@block, 'form'), partial: 'form', @@ -57,7 +60,7 @@ def destroy def block_params params.require(:block).permit( - :type, :media, :identifier, + :type, :media, :media_signed_id, :identifier, *resource_class.localized_block_attributes, *resource_class.storext_keys ) @@ -67,6 +70,11 @@ def set_block @block = set_resource_instance end + def attach_signed_media(record) + signed_id = params.dig(:block, :media_signed_id) + record.media.attach(signed_id) if signed_id.present? + end + def resource_class BetterTogether::Content::Block end diff --git a/app/javascript/controllers/better_together/image_library_controller.js b/app/javascript/controllers/better_together/image_library_controller.js new file mode 100644 index 000000000..54c78aea2 --- /dev/null +++ b/app/javascript/controllers/better_together/image_library_controller.js @@ -0,0 +1,24 @@ +import { Controller } from "@hotwired/stimulus" +// import { Modal } from "bootstrap" + +export default class extends Controller { + static targets = ["modal", "signedIdField"] + + connect() { + this.modal = new bootstrap.Modal(this.modalTarget) + } + + open() { + this.modal.show() + } + + select(event) { + const { signedId, url } = event.currentTarget.dataset + this.signedIdFieldTarget.value = signedId + const previewController = this.application.getControllerForElementAndIdentifier(this.element, "better_together--image-preview") + if (previewController && typeof previewController.previewFromUrl === "function") { + previewController.previewFromUrl(url) + } + this.modal.hide() + } +} diff --git a/app/javascript/controllers/better_together/image_preview_controller.js b/app/javascript/controllers/better_together/image_preview_controller.js index dad1c32ec..6a4fe3f80 100644 --- a/app/javascript/controllers/better_together/image_preview_controller.js +++ b/app/javascript/controllers/better_together/image_preview_controller.js @@ -80,6 +80,14 @@ export default class extends Controller { this.updateHeight(); // Update the height of the container } + previewFromUrl(url) { + this.existingImageUrl = url; + this.clearInput(); + this.previewExistingImage(); + this.deleteFieldTarget.value = '0'; + this.updateDeleteButtonState(); + } + toggleDelete() { if (this.deleteFieldTarget.value === '0' && (this.inputTarget.files.length > 0 || this.existingImageUrl)) { this.deleteFieldTarget.value = '1'; // Mark for deletion diff --git a/app/views/better_together/content/blocks/fields/_image.html.erb b/app/views/better_together/content/blocks/fields/_image.html.erb index 861054a57..2575560d0 100644 --- a/app/views/better_together/content/blocks/fields/_image.html.erb +++ b/app/views/better_together/content/blocks/fields/_image.html.erb @@ -1,6 +1,6 @@ <%- scope = local_assigns[:scope] ? local_assigns[:scope] : BetterTogether::Content::Block.block_name %> -
+
<%= label_tag do %> <%= block.class.human_attribute_name('media') %> <% if block.media.attached? %> @@ -9,6 +9,8 @@ <% end %>
<%= file_field_tag "#{scope}[media]", required: !block.media.attached?, accept: acceptable_image_file_types, "data-better_together--image-preview-target" => "input", data: { 'action' => "better_together--image-preview#preview" }, class: "form-control" %> + <%= button_tag 'Choose from library', + { type: 'button', class: 'btn btn-outline-secondary', data: { action: 'better_together--image-library#open' } } %> <%= hidden_field_tag "#{scope}[remove_media]", { value: '0' }, "data-better_together--image-preview-target" => "deleteField" %> <%= button_tag t('globals.clear'), { @@ -23,11 +25,33 @@ "data-better_together--image-preview-target" => "deleteButton", } %>
+ <%= hidden_field_tag "#{scope}[media_signed_id]", nil, "data-better_together--image-library-target" => 'signedIdField' %>
+ +
<%= render partial: 'better_together/content/blocks/fields/shared/translatable_string_field', locals: { model: block, attribute: 'alt_text', scope: } %> diff --git a/spec/requests/better_together/content/blocks_spec.rb b/spec/requests/better_together/content/blocks_spec.rb new file mode 100644 index 000000000..93807b3ae --- /dev/null +++ b/spec/requests/better_together/content/blocks_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Content Blocks', type: :request do + describe 'POST /content/blocks' do + let(:user) { create(:better_together_user, :confirmed, :platform_manager) } + + before do + login(user) + end + + it 'attaches existing blob when media_signed_id is provided' do + blob = ActiveStorage::Blob.create_and_upload!( + io: StringIO.new('fake image'), + filename: 'test.png', + content_type: 'image/png' + ) + + expect { + post better_together.content_blocks_path, params: { + block: { + type: 'BetterTogether::Content::Image', + media_signed_id: blob.signed_id + } + } + }.to change(BetterTogether::Content::Image, :count).by(1) + + block = BetterTogether::Content::Image.last + expect(block.media).to be_attached + expect(block.media.blob).to eq(blob) + end + end +end