Skip to content

Commit

Permalink
Merge pull request #2292 from dbwinger/paste-single-element
Browse files Browse the repository at this point in the history
Allow pasting into parent element with only one nested element type
  • Loading branch information
tvdeyen committed Apr 26, 2022
1 parent 969f282 commit b6800ea
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 59 deletions.
29 changes: 18 additions & 11 deletions app/controllers/alchemy/admin/elements_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def index
elements = @page_version.elements.order(:position).includes(*element_includes)
@elements = elements.not_nested.unfixed
@fixed_elements = elements.not_nested.fixed
load_clipboard_items
end

def new
Expand All @@ -20,8 +21,7 @@ def new
@parent_element = Element.find_by(id: params[:parent_element_id])
@elements = @page.available_elements_within_current_scope(@parent_element)
@element = @page_version.elements.build
@clipboard = get_clipboard("elements")
@clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
load_clipboard_items
end

# Creates a element as discribed in config/alchemy/elements.yml on page via AJAX.
Expand All @@ -44,8 +44,7 @@ def create
else
@element.page_version = @page_version
@elements = @page.available_element_definitions
@clipboard = get_clipboard("elements")
@clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
load_clipboard_items
render :new
end
end
Expand Down Expand Up @@ -126,19 +125,27 @@ def load_element
@element = Element.find(params[:id])
end

def load_clipboard_items
@clipboard = get_clipboard("elements")
@clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page)
end

def element_from_clipboard
@element_from_clipboard ||= begin
@clipboard = get_clipboard("elements")
@clipboard.detect { |item| item["id"].to_i == params[:paste_from_clipboard].to_i }
end
@clipboard = get_clipboard("elements")
@clipboard.detect { |item| item["id"].to_i == params[:paste_from_clipboard].to_i }
end
end

def paste_element_from_clipboard
@source_element = Element.find(element_from_clipboard["id"])
element = Element.copy(@source_element, {
parent_element_id: create_element_params[:parent_element_id],
page_version_id: @page_version.id,
})
element = Element.copy(
@source_element,
{
parent_element_id: create_element_params[:parent_element_id],
page_version_id: @page_version.id,
}
)
if element_from_clipboard["action"] == "cut"
@cut_element_id = @source_element.id
@clipboard.delete_if { |item| item["id"] == @source_element.id.to_s }
Expand Down
15 changes: 10 additions & 5 deletions app/models/alchemy/element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,19 +159,24 @@ def copy(source_element, differences = {})
end

def all_from_clipboard(clipboard)
return [] if clipboard.nil?
return none if clipboard.nil?

where(id: clipboard.collect { |e| e["id"] })
end

# All elements in clipboard that could be placed on page
#
def all_from_clipboard_for_page(clipboard, page)
return [] if clipboard.nil? || page.nil?
return none if clipboard.nil? || page.nil?

all_from_clipboard(clipboard).select { |ce|
page.available_element_names.include?(ce.name)
}
all_from_clipboard(clipboard).where(name: page.available_element_names)
end

# All elements in clipboard that could be placed as a child of `parent_element`
def all_from_clipboard_for_parent_element(clipboard, parent_element)
return none if clipboard.nil? || parent_element.nil?

all_from_clipboard(clipboard).where(name: parent_element.definition["nestable_elements"])
end
end

Expand Down
13 changes: 13 additions & 0 deletions app/views/alchemy/admin/clipboard/insert.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,16 @@
Alchemy.growl('<%= j Alchemy.t("item copied to clipboard", name: @item.class.name == "Alchemy::Element" ? @item.display_name_with_preview_text : @item.name) %>')
<% end -%>
$('#clipboard_button .icon').removeClass('fa-clipboard').addClass('fa-paste');

<%# Update add nested element forms for any elements that accept ONLY this as a nested element %>
<% if @item.class == Alchemy::Element %>
if (window.location.pathname == "<%= edit_admin_page_path(@item.page.id) %>") {
<%
@item.page.draft_version.elements.expanded.select do |element|
element.definition["nestable_elements"] == [@item.name]
end.each do |element|
%>
$(".add-nested-element[data-element-id='<%= element.id %>']").replaceWith('<%= j render "alchemy/admin/elements/add_nested_element_form", element: element %>')
<% end %>
}
<% end %>
27 changes: 27 additions & 0 deletions app/views/alchemy/admin/elements/_add_nested_element_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<%= content_tag :div, class: 'add-nested-element', data: { element_id: element.id } do %>
<% if element.expanded? || element.fixed? %>
<% if element.nestable_elements.length == 1 &&
(nestable_element = element.nestable_elements.first) &&
Alchemy::Element.all_from_clipboard_for_parent_element(get_clipboard("elements"), element).none?
%>
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
<%= f.hidden_field :name %>
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
<%= f.hidden_field :parent_element_id, value: element.id %>
<button class="button add-nestable-element-button" data-alchemy-button>
<%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element, scope: 'element_names')) %>
</button>
<% end %>
<% else %>
<%= link_to_dialog (nestable_element ? Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element, scope: 'element_names')) : Alchemy.t("New Element")),
alchemy.new_admin_element_path(
parent_element_id: element.id,
page_version_id: element.page_version_id
), {
size: "320x125",
title: Alchemy.t("New Element")
}, class: "button add-nestable-element-button" %>
<% end %>
<% end %>
<% end %>
24 changes: 1 addition & 23 deletions app/views/alchemy/admin/elements/_element.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,7 @@
} %>
<% end %>

<% if element.expanded? || element.fixed? %>
<% if element.nestable_elements.length == 1 %>
<% nestable_element = element.nestable_elements.first %>
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
<%= f.hidden_field :name %>
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
<%= f.hidden_field :parent_element_id, value: element.id %>
<button class="button add-nestable-element-button" data-alchemy-button>
<%= Alchemy.t(:add_nested_element) % { name: Alchemy.t(nestable_element, scope: 'element_names') } %>
</button>
<% end %>
<% else %>
<%= link_to_dialog Alchemy.t("New Element"),
alchemy.new_admin_element_path(
parent_element_id: element.id,
page_version_id: element.page_version_id
), {
size: "320x125",
title: Alchemy.t("New Element")
}, class: "button add-nestable-element-button" %>
<% end %>
<% end %>
<%= render "alchemy/admin/elements/add_nested_element_form", element: element %>
</div>
<% end %>
<% end %>
6 changes: 5 additions & 1 deletion app/views/alchemy/admin/elements/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
label: Alchemy.t(:element_of_type),
collection: elements_for_select(@elements),
prompt: Alchemy.t(:select_element),
input_html: {class: 'alchemy_selectbox', autofocus: true} %>
selected: (@elements.first if @elements.count == 1),
input_html: {class: 'alchemy_selectbox', autofocus: true, disabled: @elements.count == 1} %>
<% if @elements.count == 1 %>
<%= form.hidden_field :name, value: @elements.first[:name] %>
<% end %>
<%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
<%= form.submit Alchemy.t(:add) %>
<%- end -%>
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/alchemy/admin/elements_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ module Alchemy
let(:page_version) { create(:alchemy_page_version) }

it "assign variable for all available element definitions" do
expect_any_instance_of(Alchemy::Page).to receive(:available_element_definitions)
expect_any_instance_of(Alchemy::Page).to receive(:available_element_definitions).twice { [] }
get :new, params: { page_version_id: page_version.id }
end

Expand Down
51 changes: 44 additions & 7 deletions spec/features/admin/edit_elements_feature_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,53 @@
create(:alchemy_element, :with_nestable_elements, page_version: a_page.draft_version)
end

scenario "the add element button immediately creates the nested element.", :js do
visit alchemy.admin_elements_path(page_version_id: element.page_version_id)
button = page.find(".add-nestable-element-button")
expect(button).to have_content "Add slide"
button.click
expect(page).to have_selector(".element-editor[data-element-name='slide']")
context "when clipboard has a nestable element" do
before do
allow_any_instance_of(Alchemy::Admin::ElementsController).to receive(:get_clipboard) do
[
{ "id" => create(:alchemy_element, name: element.definition["nestable_elements"].first).id, "action" => "copy" },
]
end
end

scenario "the add button opens add element form with the clipboard tab" do
visit alchemy.admin_elements_path(page_version_id: element.page_version_id)
button = page.find(".add-nestable-element-button")
expect(button).to have_content "Add slide"
button.click
expect(page).to have_select("Element")
expect(page).to have_link("Paste from clipboard")
end
end

context "when clipboard does not have a nestable element", :js do
scenario "the add element button immediately creates the nested element." do
visit alchemy.admin_elements_path(page_version_id: element.page_version_id)
button = page.find("button.add-nestable-element-button")
expect(button).to have_content "Add slide"
button.click
expect(page).to have_selector(".element-editor[data-element-name='slide']")
end

context "when a nested element is copied to clipboard" do
before do
visit alchemy.edit_admin_page_path(element.page)
page.find(".add-nestable-element-button").click
new_element = Alchemy::Element.last
page.find("#element_#{new_element.id} .element-header").hover
page.first("a[href^='/admin/clipboard/insert?remarkable_id=#{new_element.id}&remarkable_type=elements']").click
end

scenario "the add button now opens add element form with the clipboard tab" do
find("a.add-nestable-element-button").click
expect(page).to have_select("Element")
expect(page).to have_link("Paste from clipboard")
end
end
end
end

context "With an element having multiple nestable element defined" do
context "With an element having multiple nestable elements defined" do
let!(:element) do
create(:alchemy_element,
:with_nestable_elements,
Expand Down
55 changes: 44 additions & 11 deletions spec/models/alchemy/element_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,9 @@ module Alchemy
let(:clipboard) { [{ "id" => element_1.id.to_s }, { "id" => element_2.id.to_s }] }

before do
allow(Element).to receive(:all_from_clipboard).and_return([element_1, element_2])
allow(Element).to receive(:all_from_clipboard).and_return(
Element.where(id: [element_1, element_2].map(&:id))
)
end

it "return all elements from clipboard that could be placed on page" do
Expand All @@ -411,6 +413,37 @@ module Alchemy
end
end

describe ".all_from_clipboard_for_parent_element" do
subject { Element.all_from_clipboard_for_parent_element(clipboard, parent_element) }

let(:element_1) { create(:alchemy_element) }
let(:element_2) { create(:alchemy_element, name: "slide") }
let(:clipboard) { [{ "id" => element_1.id.to_s }, { "id" => element_2.id.to_s }] }
let(:parent_element) { create :alchemy_element, name: "slider" }

before do
allow(Element).to receive(:all_from_clipboard).and_return(
Element.where(id: [element_1, element_2].map(&:id))
)
end

it "returns all elements from clipboard that can be nested in the parent element" do
expect(subject).to match_array [element_2]
end

context "when clipboard nil" do
let(:clipboard) { nil }

it { is_expected.to be_empty }
end

context "when parent_element nil" do
let(:parent_element) { nil }

it { is_expected.to be_empty }
end
end

# InstanceMethods

describe "#all_contents_by_type" do
Expand Down Expand Up @@ -480,19 +513,19 @@ module Alchemy

it "should return the translation with the translated content label" do
expect(Alchemy).to receive(:t)
.with("content_names.content", default: "Content")
.and_return("Content")
.with("content_names.content", default: "Content")
.and_return("Content")
expect(Alchemy).to receive(:t)
.with("content", scope: "content_names.article", default: "Content")
.and_return("Contenido")
.with("content", scope: "content_names.article", default: "Content")
.and_return("Contenido")
expect(Alchemy).to receive(:t)
.with("article.content.invalid", {
scope: "content_validations",
default: [:"fields.content.invalid", :"errors.invalid"],
field: "Contenido",
})
.with("article.content.invalid", {
scope: "content_validations",
default: [:"fields.content.invalid", :"errors.invalid"],
field: "Contenido",
})
expect(element).to receive(:essence_errors)
.and_return({ "content" => [:invalid] })
.and_return({ "content" => [:invalid] })

element.essence_error_messages
end
Expand Down

0 comments on commit b6800ea

Please sign in to comment.