From 4fb4006ab64cf1c3c2b8d6c077e07a422a17b9fe Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 12 Dec 2022 13:11:59 +0100 Subject: [PATCH 01/10] Add a ingredient edit view spec We have a essence picture edit view spec, but not one for the ingredient. --- .../alchemy/admin/ingredients/edit_spec.rb | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 spec/views/alchemy/admin/ingredients/edit_spec.rb diff --git a/spec/views/alchemy/admin/ingredients/edit_spec.rb b/spec/views/alchemy/admin/ingredients/edit_spec.rb new file mode 100644 index 0000000000..fcbb189836 --- /dev/null +++ b/spec/views/alchemy/admin/ingredients/edit_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "alchemy/admin/ingredients/edit.html.erb" do + before do + view.extend Alchemy::Admin::FormHelper + view.instance_variable_set(:@ingredient, ingredient) + end + + context "for a picture ingredient" do + let(:image) do + fixture_file_upload( + File.expand_path("../../../../fixtures/500x500.png", __dir__), + "image/png" + ) + end + + let(:picture) do + create(:alchemy_picture, { + image_file: image, + name: "img", + image_file_name: "img.png", + }) + end + + let(:ingredient) { Alchemy::Ingredients::Picture.new(id: 1, picture: picture) } + + it "displays render_size selection if sizes present" do + allow(ingredient).to receive(:settings).and_return({ + sizes: [ + ["Medium, 400x400", "400x400"], + ["Small, 200x200", "200x200"], + ], + }) + + render + + expect(rendered).to have_selector(".input.ingredient_render_size") + end + + it "does not display render_size selection if srcset present" do + # As the same sizes setting is used in another way here + allow(ingredient).to receive(:settings).and_return({ + sizes: ["(min-width: 600px) 600px", "100vw"], + srcset: ["200x100", "400x200", "600x300"], + }) + + render + + expect(rendered).to_not have_selector(".input.ingredient_render_size") + end + end +end From 92bbce5230eec0dc82fe1087fbc3cffef73b5138 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Wed, 14 Dec 2022 09:25:23 +0100 Subject: [PATCH 02/10] Allow to edit ingredient DOM IDs This allows to be able to link to the ingredient with an anchor. --- .../alchemy/admin/ingredients_controller.rb | 2 +- config/locales/alchemy.en.yml | 2 ++ .../admin/ingredients_controller_spec.rb | 34 +++++++++++++++---- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/app/controllers/alchemy/admin/ingredients_controller.rb b/app/controllers/alchemy/admin/ingredients_controller.rb index e0f2c60a9a..ec55f1dae3 100644 --- a/app/controllers/alchemy/admin/ingredients_controller.rb +++ b/app/controllers/alchemy/admin/ingredients_controller.rb @@ -19,7 +19,7 @@ def update private def ingredient_params - params.require(:ingredient).permit(@ingredient.class.stored_attributes[:data]) + params[:ingredient]&.permit(@ingredient.class.stored_attributes[:data]) || {} end def load_croppable_resource diff --git a/config/locales/alchemy.en.yml b/config/locales/alchemy.en.yml index d60db46705..53e1abd5c3 100644 --- a/config/locales/alchemy.en.yml +++ b/config/locales/alchemy.en.yml @@ -845,6 +845,8 @@ en: crop_from: Crop from crop_size: Crop size picture_id: Bild + alchemy/ingredient: + dom_id: Anchor alchemy/language: country_code: "Country code" language_code: "Language code" diff --git a/spec/controllers/alchemy/admin/ingredients_controller_spec.rb b/spec/controllers/alchemy/admin/ingredients_controller_spec.rb index ca7cf993ec..4d684f0941 100644 --- a/spec/controllers/alchemy/admin/ingredients_controller_spec.rb +++ b/spec/controllers/alchemy/admin/ingredients_controller_spec.rb @@ -57,23 +57,32 @@ end describe "patch :update" do + let(:ingredient) do + stub_model( + Alchemy::Ingredients::Headline, + type: "Alchemy::Ingredients::Headline", + element: element, + role: "headline", + ) + end + context "with permitted attributes" do let(:params) do { id: ingredient.id, ingredient: { - title: "new title", - css_class: "left", - link_text: "Download this file", + level: "2", + size: "3", + dom_id: "se-id", }, } end it "updates the attributes of ingredient" do patch :update, params: params, xhr: true - expect(ingredient.title).to eq "new title" - expect(ingredient.css_class).to eq "left" - expect(ingredient.link_text).to eq "Download this file" + expect(ingredient.level).to eq "2" + expect(ingredient.size).to eq "3" + expect(ingredient.dom_id).to eq "se-id" end end @@ -92,6 +101,19 @@ patch :update, params: params, xhr: true end end + + context "without attributes" do + let(:params) do + { + id: ingredient.id, + } + end + + it "does not update the attributes of ingredient" do + expect(ingredient).to receive(:update).with({}) + patch :update, params: params, xhr: true + end + end end it_behaves_like "having crop action", model_class: Alchemy::Ingredient do From f3ee864c8830b5b9621671bc6084ea16afef1017 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 12 Dec 2022 14:39:25 +0100 Subject: [PATCH 03/10] Allow to set anchors on text ingredients Text ingredients are often used as headlines or section headings. Those should be linkable with anchor links. You need to enable this in the ingredients settings - name: headline type: Text settings: anchor: true --- app/assets/stylesheets/alchemy/elements.scss | 19 ++++- app/decorators/alchemy/ingredient_editor.rb | 1 + app/models/alchemy/ingredients/text.rb | 1 + .../admin/ingredients/_text_fields.html.erb | 1 + .../alchemy/ingredients/_text_editor.html.erb | 3 + .../alchemy/ingredients/_text_view.html.erb | 10 ++- .../ingredients/shared/_anchor.html.erb | 9 +++ .../admin/ingredients_controller_spec.rb | 4 +- .../alchemy/ingredient_editor_spec.rb | 12 ++- spec/dummy/config/alchemy/elements.yml | 3 + spec/models/alchemy/ingredient_spec.rb | 2 +- spec/models/alchemy/ingredients/text_spec.rb | 7 ++ .../alchemy/ingredients/text_editor_spec.rb | 14 +++- .../alchemy/ingredients/text_view_spec.rb | 76 +++++++++++++++++-- 14 files changed, 145 insertions(+), 17 deletions(-) create mode 100644 app/views/alchemy/admin/ingredients/_text_fields.html.erb create mode 100644 app/views/alchemy/ingredients/shared/_anchor.html.erb diff --git a/app/assets/stylesheets/alchemy/elements.scss b/app/assets/stylesheets/alchemy/elements.scss index 8fb3aaea57..364e968a1c 100644 --- a/app/assets/stylesheets/alchemy/elements.scss +++ b/app/assets/stylesheets/alchemy/elements.scss @@ -714,6 +714,18 @@ select.long { input[type="text"] { padding-right: 2 * ($form-field-addon-width + $default-padding); } + + &.with-anchor { + input[type="text"] { + padding-right: 26px + 2 * ($form-field-addon-width + $default-padding); + } + } + } + + &.with-anchor { + input[type="text"] { + padding-right: 26px; + } } &.missing { @@ -962,9 +974,14 @@ textarea.has_tinymce { } .ingredient-date--label, -.essence_date--label { +.essence_date--label, +.edit-ingredient-anchor-link { position: absolute; right: 2 * $default-padding; bottom: $form-field-height / 2; margin: 0 !important; + + .linkable & { + right: 2 * $form-field-addon-width + $default-padding; + } } diff --git a/app/decorators/alchemy/ingredient_editor.rb b/app/decorators/alchemy/ingredient_editor.rb index 4c6c3f11a5..bf30fb9fc9 100644 --- a/app/decorators/alchemy/ingredient_editor.rb +++ b/app/decorators/alchemy/ingredient_editor.rb @@ -39,6 +39,7 @@ def css_classes respond_to?(:level_options) && level_options.many? ? "with-level-select" : nil, respond_to?(:size_options) && size_options.many? ? "with-size-select" : nil, settings[:linkable] ? "linkable" : nil, + settings[:anchor] ? "with-anchor" : nil, ].compact end diff --git a/app/models/alchemy/ingredients/text.rb b/app/models/alchemy/ingredients/text.rb index af2c3c445c..050b47e88d 100644 --- a/app/models/alchemy/ingredients/text.rb +++ b/app/models/alchemy/ingredients/text.rb @@ -8,6 +8,7 @@ module Ingredients # class Text < Alchemy::Ingredient store_accessor :data, + :dom_id, :link, :link_target, :link_title, diff --git a/app/views/alchemy/admin/ingredients/_text_fields.html.erb b/app/views/alchemy/admin/ingredients/_text_fields.html.erb new file mode 100644 index 0000000000..ecbbf2e59c --- /dev/null +++ b/app/views/alchemy/admin/ingredients/_text_fields.html.erb @@ -0,0 +1 @@ +<%= f.input :dom_id %> diff --git a/app/views/alchemy/ingredients/_text_editor.html.erb b/app/views/alchemy/ingredients/_text_editor.html.erb index a2e0448342..5724c7dd77 100644 --- a/app/views/alchemy/ingredients/_text_editor.html.erb +++ b/app/views/alchemy/ingredients/_text_editor.html.erb @@ -9,6 +9,9 @@ class: text_editor.settings[:linkable] ? "text_with_icon" : "", id: nil, type: text_editor.settings[:input_type] || "text" %> + <% if text_editor.settings[:anchor] %> + <%= render "alchemy/ingredients/shared/anchor", ingredient_editor: text_editor %> + <% end %> <% if text_editor.settings[:linkable] %> <%= f.hidden_field :link, "data-link-value": true, id: nil %> <%= f.hidden_field :link_title, "data-link-title": true, id: nil %> diff --git a/app/views/alchemy/ingredients/_text_view.html.erb b/app/views/alchemy/ingredients/_text_view.html.erb index 00f48e3777..e17a4edaab 100644 --- a/app/views/alchemy/ingredients/_text_view.html.erb +++ b/app/views/alchemy/ingredients/_text_view.html.erb @@ -1,13 +1,17 @@ <%- options = local_assigns.fetch(:options, {}) -%> <%- html_options = local_assigns.fetch(:html_options, {}) -%> -<%- if text_view.link.blank? || - text_view.settings_value(:disable_link, options) -%> -<%= text_view.value -%> +<%- if text_view.link.blank? || text_view.settings_value(:disable_link, options) -%> + <%- if text_view.dom_id.present? -%> + <%= content_tag :a, text_view.value, id: text_view.dom_id %> + <% else %> + <%= text_view.value -%> + <%- end -%> <%- else -%> <%= link_to( text_view.value, url_for(text_view.link), { + id: text_view.dom_id.presence, title: text_view.link_title, target: (text_view.link_target == "blank" ? "_blank" : nil), 'data-link-target' => text_view.link_target diff --git a/app/views/alchemy/ingredients/shared/_anchor.html.erb b/app/views/alchemy/ingredients/shared/_anchor.html.erb new file mode 100644 index 0000000000..002d52b9b9 --- /dev/null +++ b/app/views/alchemy/ingredients/shared/_anchor.html.erb @@ -0,0 +1,9 @@ + + <%= link_to_dialog render_icon(:bookmark, { style: ingredient_editor.dom_id.present? ? "solid" : "regular" }), + alchemy.edit_admin_ingredient_path(id: ingredient_editor.id), + { + title: Alchemy.t(:edit_anchor), + size: "380x125" + }, + title: Alchemy.t(:edit_anchor) %> + diff --git a/spec/controllers/alchemy/admin/ingredients_controller_spec.rb b/spec/controllers/alchemy/admin/ingredients_controller_spec.rb index 4d684f0941..ba3d6499cd 100644 --- a/spec/controllers/alchemy/admin/ingredients_controller_spec.rb +++ b/spec/controllers/alchemy/admin/ingredients_controller_spec.rb @@ -60,9 +60,9 @@ let(:ingredient) do stub_model( Alchemy::Ingredients::Headline, - type: "Alchemy::Ingredients::Headline", + type: "Alchemy::Ingredients::Text", element: element, - role: "headline", + role: "text", ) end diff --git a/spec/decorators/alchemy/ingredient_editor_spec.rb b/spec/decorators/alchemy/ingredient_editor_spec.rb index 584572f3ce..f260319abe 100644 --- a/spec/decorators/alchemy/ingredient_editor_spec.rb +++ b/spec/decorators/alchemy/ingredient_editor_spec.rb @@ -60,13 +60,23 @@ context "when linkable" do before do - expect(ingredient).to receive(:settings) do + expect(ingredient).to receive(:settings).at_least(:once) do { linkable: true } end end it { is_expected.to include("linkable") } end + + context "when with anchor" do + before do + expect(ingredient).to receive(:settings).at_least(:once) do + { anchor: true } + end + end + + it { is_expected.to include("with-anchor") } + end end describe "#data_attributes" do diff --git a/spec/dummy/config/alchemy/elements.yml b/spec/dummy/config/alchemy/elements.yml index 6dcb793c05..6e26dce43b 100644 --- a/spec/dummy/config/alchemy/elements.yml +++ b/spec/dummy/config/alchemy/elements.yml @@ -136,6 +136,9 @@ - role: text type: Text hint: true + settings: + anchor: true + linkable: true validate: - format: !ruby/regexp '/\w+/i' - role: picture diff --git a/spec/models/alchemy/ingredient_spec.rb b/spec/models/alchemy/ingredient_spec.rb index 8cb5c41ffe..8f4653e177 100644 --- a/spec/models/alchemy/ingredient_spec.rb +++ b/spec/models/alchemy/ingredient_spec.rb @@ -57,7 +57,7 @@ let(:ingredient) { Alchemy::Ingredients::Text.new(role: "headline", element: element) } it "returns the settings hash from definition" do - expect(ingredient.settings).to eq({ "linkable" => true }) + expect(ingredient.settings).to eq({ "anchor" => "from_value", "linkable" => true }) end context "if settings are not defined" do diff --git a/spec/models/alchemy/ingredients/text_spec.rb b/spec/models/alchemy/ingredients/text_spec.rb index 9f8f874b73..22af1ccae5 100644 --- a/spec/models/alchemy/ingredients/text_spec.rb +++ b/spec/models/alchemy/ingredients/text_spec.rb @@ -14,6 +14,7 @@ role: "headline", value: "A brown fox quickly jumps over the lazy dog", data: { + dom_id: "se-anchor", link: "https://example.com", link_target: "_blank", link_title: "Click here", @@ -22,6 +23,12 @@ ) end + describe "#dom_id" do + subject { text_ingredient.dom_id } + + it { is_expected.to eq("se-anchor") } + end + describe "#link" do subject { text_ingredient.link } diff --git a/spec/views/alchemy/ingredients/text_editor_spec.rb b/spec/views/alchemy/ingredients/text_editor_spec.rb index 8b2640bbc5..6174dfb87d 100644 --- a/spec/views/alchemy/ingredients/text_editor_spec.rb +++ b/spec/views/alchemy/ingredients/text_editor_spec.rb @@ -5,7 +5,7 @@ RSpec.describe "alchemy/ingredients/_text_editor" do let(:element) { build_stubbed(:alchemy_element, name: "all_you_can_eat_ingredients") } let(:element_editor) { Alchemy::ElementEditor.new(element) } - let(:ingredient) { Alchemy::Ingredients::Text.new(role: "headline", value: "1234", element: element) } + let(:ingredient) { Alchemy::Ingredients::Text.new(id: 1, role: "headline", value: "1234", element: element) } let(:settings) { {} } it_behaves_like "an alchemy ingredient editor" @@ -50,4 +50,16 @@ expect(rendered).to have_selector('input[type="hidden"][name="element[ingredients_attributes][0][link_target]"]') end end + + context "with settings anchor set to true" do + let(:settings) do + { + anchor: true, + } + end + + it "renders anchor button" do + expect(rendered).to have_selector(".edit-ingredient-anchor-link a") + end + end end diff --git a/spec/views/alchemy/ingredients/text_view_spec.rb b/spec/views/alchemy/ingredients/text_view_spec.rb index ac37f155b6..93ebe7217c 100644 --- a/spec/views/alchemy/ingredients/text_view_spec.rb +++ b/spec/views/alchemy/ingredients/text_view_spec.rb @@ -6,10 +6,29 @@ let(:ingredient) { Alchemy::Ingredients::Text.new(value: "Hello World") } context "with blank link value" do - it "only renders the value" do - render ingredient - expect(rendered).to have_content("Hello World") - expect(rendered).to_not have_selector("a") + context "and dom id set" do + let(:ingredient) do + Alchemy::Ingredients::Text.new( + value: "Hello World", + data: { + dom_id: "se-anchor", + }, + ) + end + + it "renders the dom_id and the value" do + render ingredient, options: { disable_link: true } + expect(rendered).to have_content("Hello World") + expect(rendered).to have_selector('a[id="se-anchor"]') + end + end + + context "and no dom id set" do + it "only renders the value" do + render ingredient + expect(rendered).to have_content("Hello World") + expect(rendered).to_not have_selector("a") + end end end @@ -39,10 +58,32 @@ end context "but with options disable_link set to true" do - it "only renders the value" do - render ingredient, options: { disable_link: true } - expect(rendered).to have_content("Hello World") - expect(rendered).to_not have_selector("a") + context "and dom id set" do + let(:ingredient) do + Alchemy::Ingredients::Text.new( + value: "Hello World", + data: { + dom_id: "se-anchor", + link: "http://google.com", + link_title: "Foo", + link_target: "blank", + }, + ) + end + + it "renders the dom_id and the value" do + render ingredient, options: { disable_link: true } + expect(rendered).to have_content("Hello World") + expect(rendered).to have_selector('a[id="se-anchor"]') + end + end + + context "and no dom id set" do + it "only renders the value" do + render ingredient, options: { disable_link: true } + expect(rendered).to have_content("Hello World") + expect(rendered).to_not have_selector("a") + end end end @@ -57,5 +98,24 @@ expect(rendered).to_not have_selector("a") end end + + context "and a dom id set" do + let(:ingredient) do + Alchemy::Ingredients::Text.new( + value: "Hello World", + data: { + dom_id: "se-anchor", + link: "http://google.com", + link_title: "Foo", + link_target: "blank", + }, + ) + end + + it "renders the dom_id" do + render ingredient + expect(rendered).to have_selector('a[id="se-anchor"]') + end + end end end From 89900a11cbecd3d4bddf39ca3d963c996e53982b Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 12 Dec 2022 16:41:11 +0100 Subject: [PATCH 04/10] Allow to set anchors on headline ingredients Headlines are often linked to with anchor links. You need to enable this in the ingredients settings - name: headline type: Headline settings: anchor: true --- app/assets/stylesheets/alchemy/elements.scss | 50 +++++++- app/models/alchemy/ingredients/headline.rb | 3 + app/models/alchemy/ingredients/text.rb | 2 + app/models/concerns/alchemy/dom_ids.rb | 32 +++++ .../admin/ingredients/_dom_id_fields.html.erb | 4 + .../ingredients/_headline_fields.html.erb | 3 + .../admin/ingredients/_text_fields.html.erb | 4 +- .../ingredients/_headline_editor.html.erb | 6 + .../ingredients/_headline_view.html.erb | 1 + config/locales/alchemy.en.yml | 1 + .../test_support/shared_dom_ids_examples.rb | 119 ++++++++++++++++++ spec/dummy/config/alchemy/elements.yml | 1 - .../alchemy/ingredients/headline_spec.rb | 8 ++ spec/models/alchemy/ingredients/text_spec.rb | 1 + spec/rails_helper.rb | 1 + .../ingredients/headline_editor_spec.rb | 12 ++ .../alchemy/ingredients/headline_view_spec.rb | 9 +- 17 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 app/models/concerns/alchemy/dom_ids.rb create mode 100644 app/views/alchemy/admin/ingredients/_dom_id_fields.html.erb create mode 100644 app/views/alchemy/admin/ingredients/_headline_fields.html.erb create mode 100644 lib/alchemy/test_support/shared_dom_ids_examples.rb diff --git a/app/assets/stylesheets/alchemy/elements.scss b/app/assets/stylesheets/alchemy/elements.scss index 364e968a1c..8282d3a597 100644 --- a/app/assets/stylesheets/alchemy/elements.scss +++ b/app/assets/stylesheets/alchemy/elements.scss @@ -606,6 +606,12 @@ input[type="text"] { padding-right: $form-field-addon-width + 2 * $default-padding; } + + &.with-anchor { + input[type="text"] { + padding-right: 22px + $form-field-addon-width + $default-padding; + } + } } &.with-size-select { @@ -613,10 +619,22 @@ padding-right: $form-field-addon-width + 2 * $default-padding; } + &.with-anchor { + input[type="text"] { + padding-right: 22px + $form-field-addon-width + $default-padding; + } + } + &.with-level-select { input[type="text"] { padding-right: 2 * ($form-field-addon-width + $default-padding); } + + &.with-anchor { + input[type="text"] { + padding-right: 16px + 2 * ($form-field-addon-width + $default-padding); + } + } } } } @@ -978,10 +996,40 @@ textarea.has_tinymce { .edit-ingredient-anchor-link { position: absolute; right: 2 * $default-padding; - bottom: $form-field-height / 2; + bottom: 15px; margin: 0 !important; +} + +.edit-ingredient-anchor-link { + right: $default-padding; + + > a { + padding: $default-padding; + } .linkable & { right: 2 * $form-field-addon-width + $default-padding; } + + .with-size-select & { + right: $form-field-addon-width + $default-padding; + } + + .with-level-select & { + right: $form-field-addon-width + $default-padding; + } + + .with-level-select.with-size-select & { + right: 2 * $form-field-addon-width + $default-padding; + } +} + +.ingredient-properties-link { + position: absolute; + right: 2px; + bottom: 15px; + + > a { + padding: $default-padding; + } } diff --git a/app/models/alchemy/ingredients/headline.rb b/app/models/alchemy/ingredients/headline.rb index 5afffd7349..79c529f8ae 100644 --- a/app/models/alchemy/ingredients/headline.rb +++ b/app/models/alchemy/ingredients/headline.rb @@ -5,7 +5,10 @@ module Ingredients # A text headline # class Headline < Alchemy::Ingredient + include DomIds + store_accessor :data, + :dom_id, :level, :size diff --git a/app/models/alchemy/ingredients/text.rb b/app/models/alchemy/ingredients/text.rb index 050b47e88d..de66c01a95 100644 --- a/app/models/alchemy/ingredients/text.rb +++ b/app/models/alchemy/ingredients/text.rb @@ -7,6 +7,8 @@ module Ingredients # Optionally it can have a link # class Text < Alchemy::Ingredient + include DomIds + store_accessor :data, :dom_id, :link, diff --git a/app/models/concerns/alchemy/dom_ids.rb b/app/models/concerns/alchemy/dom_ids.rb new file mode 100644 index 0000000000..01a53efdab --- /dev/null +++ b/app/models/concerns/alchemy/dom_ids.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Alchemy + module DomIds + extend ActiveSupport::Concern + + RESERVED_ANCHOR_SETTING_VALUES = %w[false from_value true] + + included do + before_validation :parameterize_dom_id, + if: -> { settings[:anchor].to_s == "true" } + before_validation :set_dom_id_from_value, + if: -> { settings[:anchor].to_s == "from_value" } + before_validation :set_dom_id_to_fixed_value, + if: -> { !RESERVED_ANCHOR_SETTING_VALUES.include? settings[:anchor].to_s } + end + + private + + def parameterize_dom_id + self.dom_id = dom_id&.parameterize + end + + def set_dom_id_from_value + self.dom_id = value&.parameterize + end + + def set_dom_id_to_fixed_value + self.dom_id = settings[:anchor]&.parameterize + end + end +end diff --git a/app/views/alchemy/admin/ingredients/_dom_id_fields.html.erb b/app/views/alchemy/admin/ingredients/_dom_id_fields.html.erb new file mode 100644 index 0000000000..969266cff4 --- /dev/null +++ b/app/views/alchemy/admin/ingredients/_dom_id_fields.html.erb @@ -0,0 +1,4 @@ +<% disabled = ingredient.settings[:anchor].to_s != "true" %> +<%= f.input :dom_id, + disabled: disabled, + hint: disabled && Alchemy.t(:automatic_anchor_notice) %> diff --git a/app/views/alchemy/admin/ingredients/_headline_fields.html.erb b/app/views/alchemy/admin/ingredients/_headline_fields.html.erb new file mode 100644 index 0000000000..c6ecfb8d8e --- /dev/null +++ b/app/views/alchemy/admin/ingredients/_headline_fields.html.erb @@ -0,0 +1,3 @@ +<% if ingredient.settings[:anchor] %> + <%= render "dom_id_fields", f: f, ingredient: ingredient %> +<% end %> diff --git a/app/views/alchemy/admin/ingredients/_text_fields.html.erb b/app/views/alchemy/admin/ingredients/_text_fields.html.erb index ecbbf2e59c..c6ecfb8d8e 100644 --- a/app/views/alchemy/admin/ingredients/_text_fields.html.erb +++ b/app/views/alchemy/admin/ingredients/_text_fields.html.erb @@ -1 +1,3 @@ -<%= f.input :dom_id %> +<% if ingredient.settings[:anchor] %> + <%= render "dom_id_fields", f: f, ingredient: ingredient %> +<% end %> diff --git a/app/views/alchemy/ingredients/_headline_editor.html.erb b/app/views/alchemy/ingredients/_headline_editor.html.erb index 2b3a2eb0eb..a0ad4c383d 100644 --- a/app/views/alchemy/ingredients/_headline_editor.html.erb +++ b/app/views/alchemy/ingredients/_headline_editor.html.erb @@ -7,6 +7,11 @@ <%= element_form.fields_for(:ingredients, headline_editor.ingredient) do |f| %> <%= ingredient_label(headline_editor) %> <%= f.text_field :value, id: nil %> + + <% if headline_editor.settings[:anchor] %> + <%= render "alchemy/ingredients/shared/anchor", ingredient_editor: headline_editor %> + <% end %> + <% if has_level_select %>
"> <%= f.select :level, @@ -15,6 +20,7 @@ { class: "custom-select", title: f.object.class.human_attribute_name(:level) } %>
<% end %> + <% if has_size_select %>
<%= f.select :size, options_for_select(headline_editor.size_options, headline_editor.size), diff --git a/app/views/alchemy/ingredients/_headline_view.html.erb b/app/views/alchemy/ingredients/_headline_view.html.erb index 5190bbfa86..76fd3138f9 100644 --- a/app/views/alchemy/ingredients/_headline_view.html.erb +++ b/app/views/alchemy/ingredients/_headline_view.html.erb @@ -2,6 +2,7 @@ <%= content_tag "h#{headline_view.level}", headline_view.value, + id: headline_view.dom_id.presence, class: [ headline_view.size ? "h#{headline_view.size}" : nil, html_options[:class] diff --git a/config/locales/alchemy.en.yml b/config/locales/alchemy.en.yml index 53e1abd5c3..3dec327953 100644 --- a/config/locales/alchemy.en.yml +++ b/config/locales/alchemy.en.yml @@ -229,6 +229,7 @@ en: add_nested_element: "Add %{name}" anchor: 'Anchor' anchor_link_headline: "You can link to an element anchor from the actual page." + automatic_anchor_notice: The anchor is generated automatically. attribute_fixed: Value can't be changed for this page type back: 'back' locked_pages: "Active pages" diff --git a/lib/alchemy/test_support/shared_dom_ids_examples.rb b/lib/alchemy/test_support/shared_dom_ids_examples.rb new file mode 100644 index 0000000000..282d25166b --- /dev/null +++ b/lib/alchemy/test_support/shared_dom_ids_examples.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for "having dom ids" do + let(:element) { build(:alchemy_element, name: "element_with_ingredients") } + + let(:ingredient) do + described_class.new( + element: element, + role: "headline", + ) + end + + describe "setting dom id from value" do + subject do + ingredient.valid? && ingredient.dom_id + end + + before do + expect_any_instance_of(described_class).to receive(:settings).at_least(:once) { settings } + end + + context "without anchor settings" do + let(:settings) do + {} + end + + it "does not set a dom_id" do + is_expected.to be_nil + end + end + + context "with anchor setting set to true" do + let(:settings) do + { anchor: true } + end + + it "parameterizes dom_id" do + ingredient.dom_id = "SE Headline" + is_expected.to eq "se-headline" + end + end + + context "with anchor setting set to from_value" do + let(:settings) do + { anchor: "from_value" } + end + + context "with a value present" do + let(:ingredient) do + described_class.new( + element: element, + role: "headline", + value: "Hello World", + ) + end + + it "sets a dom_id from value" do + is_expected.to eq "hello-world" + end + end + + context "with no value present" do + let(:ingredient) do + described_class.new( + element: element, + role: "headline", + value: "", + ) + end + + it "sets no dom_id" do + is_expected.to eq "" + end + end + end + + context "with anchor setting set to fixed value" do + context "that is false" do + let(:settings) do + { anchor: false } + end + + it "sets no dom_id" do + is_expected.to be_nil + end + end + + context "that is true" do + let(:settings) do + { anchor: true } + end + + it "sets no dom_id" do + is_expected.to be_nil + end + end + + context "that is from_value" do + let(:settings) do + { anchor: true } + end + + it "sets no dom_id" do + is_expected.to be_nil + end + end + + context "that is a non reserved value" do + let(:settings) do + { anchor: "FixED VALUE" } + end + + it "sets the dom_id to fixed value" do + is_expected.to eq "fixed-value" + end + end + end + end +end diff --git a/spec/dummy/config/alchemy/elements.yml b/spec/dummy/config/alchemy/elements.yml index 6e26dce43b..89b40f8f6a 100644 --- a/spec/dummy/config/alchemy/elements.yml +++ b/spec/dummy/config/alchemy/elements.yml @@ -298,4 +298,3 @@ - name: key_words type: EssenceText group: details - diff --git a/spec/models/alchemy/ingredients/headline_spec.rb b/spec/models/alchemy/ingredients/headline_spec.rb index 0fae5c558f..d46708ab2d 100644 --- a/spec/models/alchemy/ingredients/headline_spec.rb +++ b/spec/models/alchemy/ingredients/headline_spec.rb @@ -6,6 +6,7 @@ subject(:ingredient) do described_class.new( value: value, + dom_id: "se-headline", level: 2, size: 3, ) @@ -14,6 +15,13 @@ let(:value) { "A headline" } it_behaves_like "an alchemy ingredient" + it_behaves_like "having dom ids" + + describe "#dom_id" do + subject { ingredient.dom_id } + + it { is_expected.to eq("se-headline") } + end describe "#level_options" do subject { ingredient.level_options } diff --git a/spec/models/alchemy/ingredients/text_spec.rb b/spec/models/alchemy/ingredients/text_spec.rb index 22af1ccae5..caf8b6d968 100644 --- a/spec/models/alchemy/ingredients/text_spec.rb +++ b/spec/models/alchemy/ingredients/text_spec.rb @@ -4,6 +4,7 @@ RSpec.describe Alchemy::Ingredients::Text do it_behaves_like "an alchemy ingredient" + it_behaves_like "having dom ids" let(:element) { build(:alchemy_element, name: "element_with_ingredients") } diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 6cd534f92f..1c6d86bca3 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -22,6 +22,7 @@ require "alchemy/test_support/essence_shared_examples" require "alchemy/test_support/having_crop_action_examples" require "alchemy/test_support/having_picture_thumbnails_examples" +require "alchemy/test_support/shared_dom_ids_examples" require "alchemy/test_support/shared_ingredient_examples" require "alchemy/test_support/shared_ingredient_editor_examples" require "alchemy/test_support/integration_helpers" diff --git a/spec/views/alchemy/ingredients/headline_editor_spec.rb b/spec/views/alchemy/ingredients/headline_editor_spec.rb index 81207d6d4e..7617856bc8 100644 --- a/spec/views/alchemy/ingredients/headline_editor_spec.rb +++ b/spec/views/alchemy/ingredients/headline_editor_spec.rb @@ -61,4 +61,16 @@ is_expected.to have_selector("select[name='element[ingredients_attributes][0][size]']") end end + + context "with settings anchor set to true" do + let(:settings) do + { + anchor: true, + } + end + + it "renders anchor link button" do + is_expected.to have_selector(".edit-ingredient-anchor-link a") + end + end end diff --git a/spec/views/alchemy/ingredients/headline_view_spec.rb b/spec/views/alchemy/ingredients/headline_view_spec.rb index 19679e4a33..f35f2ffb06 100644 --- a/spec/views/alchemy/ingredients/headline_view_spec.rb +++ b/spec/views/alchemy/ingredients/headline_view_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" RSpec.describe "alchemy/ingredients/_headline_view" do - let(:ingredient) { Alchemy::Ingredients::Headline.new(value: "Hello", level: 2) } + let(:ingredient) { Alchemy::Ingredients::Headline.new(value: "Hello", level: 2, dom_id: "se-headline") } it "renders headline for level" do render ingredient @@ -11,6 +11,13 @@ expect(rendered).to have_content("Hello") end + context "with dom_id" do + it "adds id" do + render ingredient + expect(rendered).to have_selector("h2#se-headline") + end + end + context "without size" do it "does not add size class" do render ingredient From 0133dd5d07bf3c481ea936f042f04575c85945f8 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 12 Dec 2022 19:05:19 +0100 Subject: [PATCH 05/10] Add ingredients API controller Will be used by admins only to load ingredients for the DOM ID select in the link dialog. --- .../alchemy/api/ingredients_controller.rb | 22 +++++++ .../alchemy/ingredient_serializer.rb | 11 ++++ .../alchemy/admin/partials/_routes.html.erb | 3 +- config/routes.rb | 1 + .../api/ingredients_controller_spec.rb | 66 +++++++++++++++++++ 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 app/controllers/alchemy/api/ingredients_controller.rb create mode 100644 app/serializers/alchemy/ingredient_serializer.rb create mode 100644 spec/controllers/alchemy/api/ingredients_controller_spec.rb diff --git a/app/controllers/alchemy/api/ingredients_controller.rb b/app/controllers/alchemy/api/ingredients_controller.rb new file mode 100644 index 0000000000..94de9260af --- /dev/null +++ b/app/controllers/alchemy/api/ingredients_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Alchemy + class Api::IngredientsController < Api::BaseController + # Returns all ingredients as json object + # + # You can either load all or only these for :element_id or :page_id param + # + def index + @ingredients = Alchemy::Ingredient.accessible_by(current_ability, :index) + + if params[:page_id].present? + @ingredients = @ingredients + .where(alchemy_page_versions: { page_id: params[:page_id] }) + .merge(Alchemy::PageVersion.drafts) + .joins(element: :page_version) + end + + render json: @ingredients, adapter: :json, root: "ingredients" + end + end +end diff --git a/app/serializers/alchemy/ingredient_serializer.rb b/app/serializers/alchemy/ingredient_serializer.rb new file mode 100644 index 0000000000..45865d2ed8 --- /dev/null +++ b/app/serializers/alchemy/ingredient_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Alchemy + class IngredientSerializer < ActiveModel::Serializer + attributes :id, + :role, + :value, + :element_id, + :data + end +end diff --git a/app/views/alchemy/admin/partials/_routes.html.erb b/app/views/alchemy/admin/partials/_routes.html.erb index 16b0fabd80..f518104735 100644 --- a/app/views/alchemy/admin/partials/_routes.html.erb +++ b/app/views/alchemy/admin/partials/_routes.html.erb @@ -34,6 +34,7 @@ order_admin_elements_path: '<%= alchemy.order_admin_elements_path %>', link_admin_pages_path: '<%= alchemy.link_admin_pages_path %>', api_pages_path: '<%= alchemy.api_pages_path %>', - api_elements_path: '<%= alchemy.api_elements_path %>' + api_elements_path: '<%= alchemy.api_elements_path %>', + api_ingredients_path: '<%= alchemy.api_ingredients_path %>' }; diff --git a/config/routes.rb b/config/routes.rb index 98a06b9ff3..ba60ca4cde 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -126,6 +126,7 @@ namespace :api, defaults: { format: "json" } do resources :contents, only: [:index, :show] + resources :ingredients, only: [:index] resources :elements, only: [:index, :show] do get "/contents" => "contents#index", as: "contents" diff --git a/spec/controllers/alchemy/api/ingredients_controller_spec.rb b/spec/controllers/alchemy/api/ingredients_controller_spec.rb new file mode 100644 index 0000000000..9088314b2b --- /dev/null +++ b/spec/controllers/alchemy/api/ingredients_controller_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Alchemy::Api::IngredientsController do + routes { Alchemy::Engine.routes } + + describe "#index" do + let(:page) { create(:alchemy_page) } + let(:element) { create(:alchemy_element, page_version: page.draft_version) } + let!(:ingredient) { create(:alchemy_ingredient_text, element: element) } + + context "as guest user" do + it "returns no ingredients" do + get :index, params: { format: :json } + + expect(response.status).to eq(200) + expect(response.media_type).to eq("application/json") + + result = JSON.parse(response.body) + + expect(result).to have_key("ingredients") + expect(result["ingredients"].size).to be_zero + end + end + + context "as author" do + before do + authorize_user(build(:alchemy_dummy_user, :as_author)) + end + + it "returns all ingredients" do + get :index, params: { format: :json } + + expect(response.status).to eq(200) + expect(response.media_type).to eq("application/json") + + result = JSON.parse(response.body) + + expect(result).to have_key("ingredients") + expect(result["ingredients"].size).to eq Alchemy::Ingredient.count + end + + context "with page_id" do + let(:public_page) { create(:alchemy_page, :public) } + let(:other_draft_element) { create(:alchemy_element, page_version: public_page.draft_version) } + let(:other_public_element) { create(:alchemy_element, page_version: public_page.public_version) } + let!(:other_draft_ingredient) { create(:alchemy_ingredient_text, element: other_draft_element) } + let!(:other_public_ingredient) { create(:alchemy_ingredient_text, element: other_public_element) } + + it "returns only draft ingredients from this page" do + get :index, params: { page_id: public_page.id, format: :json } + + expect(response.status).to eq(200) + expect(response.media_type).to eq("application/json") + + result = JSON.parse(response.body) + + expect(result).to have_key("ingredients") + expect(result["ingredients"].size).to eq(1) + expect(result["ingredients"][0]["element_id"]).to eq(other_draft_element.id) + end + end + end + end +end From c3a3178c733ad2db5462e2c075abf1f2d8d1a697 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 12 Dec 2022 19:28:30 +0100 Subject: [PATCH 06/10] Update link dialog "Anchor Link" feature We now have ingredients that have dom_id stored that we can use as anchor in the link dialog. --- .../alchemy/alchemy.link_dialog.js.coffee | 24 ++++++++++--------- config/locales/alchemy.en.yml | 4 ++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee index 9e648e439f..e8de6a586f 100644 --- a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee @@ -100,7 +100,7 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog (data) => page = data.pages[0] if page - @initElementSelect(page.id) + @initDomIdSelect(page.id) callback id: page.url_path name: page.name @@ -116,16 +116,19 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog @$element_anchor.select2('destroy').prop('disabled', true) else @$element_anchor.val('') - @initElementSelect(event.added.page_id) + @initDomIdSelect(event.added.page_id) - # Initializes the select2 based elements select + # Initializes the select2 based dom id select # reveals after a page has been selected - initElementSelect: (page_id) -> - $.get Alchemy.routes.api_elements_path, page_id: page_id, (data) => + initDomIdSelect: (page_id) -> + $.get Alchemy.routes.api_ingredients_path, page_id: page_id, (data) => + dom_ids = data.ingredients.filter (ingredient) -> + ingredient.data?.dom_id + .map (ingredient) -> + id: ingredient.data.dom_id + text: "##{ingredient.data.dom_id}" @$element_anchor.prop('disabled', false).removeAttr('placeholder').select2 - data: [ id: '', text: Alchemy.t('None') ].concat data.elements.map (element) -> - id: element.dom_id - text: element.display_name + data: [ id: '', text: Alchemy.t('None') ].concat(dom_ids) # Creates a link if no validation errors are present. # Otherwise shows an error notice. @@ -238,11 +241,10 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog # Populates the internal anchors select initAnchorLinks: -> frame = document.getElementById('alchemy_preview_window') - elements = frame.contentDocument?.getElementsByTagName('*') || [] + elements = frame.contentDocument?.querySelectorAll('[id]') || [] if elements.length > 0 for element in elements - if element.id - @$anchor_link.append("") + @$anchor_link.append("") else @$anchor_link.html("") return diff --git a/config/locales/alchemy.en.yml b/config/locales/alchemy.en.yml index 3dec327953..f171dc76ba 100644 --- a/config/locales/alchemy.en.yml +++ b/config/locales/alchemy.en.yml @@ -228,7 +228,7 @@ en: add_nested_element: "Add %{name}" anchor: 'Anchor' - anchor_link_headline: "You can link to an element anchor from the actual page." + anchor_link_headline: You can link to an anchor from the current page. automatic_anchor_notice: The anchor is generated automatically. attribute_fixed: Value can't be changed for this page type back: 'back' @@ -468,7 +468,7 @@ en: image_name: "Name: %{name}" image_title: "Title-tag" internal_link_headline: "Search for a page to link to by entering its name into the Page select." - internal_link_page_elements_explanation: "Additionally you can choose an anchor to an element from selected page." + internal_link_page_elements_explanation: "Additionally you can choose an anchor to link to from selected page." "item copied to clipboard": "Copied %{name} to clipboard" "item moved to clipboard": "Moved %{name} to clipboard" "item removed from clipboard": "Removed %{name} from clipboard" From c3b9916ff5d838a1561a0ea404e320c5bd7be198 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Wed, 14 Dec 2022 10:44:13 +0100 Subject: [PATCH 07/10] Update anchor link icon after update --- app/views/alchemy/admin/ingredients/update.js.erb | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 app/views/alchemy/admin/ingredients/update.js.erb diff --git a/app/views/alchemy/admin/ingredients/update.js.erb b/app/views/alchemy/admin/ingredients/update.js.erb new file mode 100644 index 0000000000..42d50774e2 --- /dev/null +++ b/app/views/alchemy/admin/ingredients/update.js.erb @@ -0,0 +1,8 @@ +Alchemy.closeCurrentDialog( +<% if @ingredient.settings[:anchor] %> + function() { + const ingredientEditor = document.querySelector('[data-ingredient-id="<%= @ingredient.id %>"]'); + ingredientEditor.querySelector('.edit-ingredient-anchor-link a').innerHTML = '<%= j render_icon(:bookmark, { style: @ingredient.dom_id.present? ? "solid" : "regular" }) %>'; + } +<% end %> +); From 423398921977472d81e11d4a80a36d82e1101500 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Thu, 15 Dec 2022 14:27:25 +0100 Subject: [PATCH 08/10] Silence ingredient upgrader in tests --- lib/alchemy/upgrader/tasks/ingredients_migrator.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/alchemy/upgrader/tasks/ingredients_migrator.rb b/lib/alchemy/upgrader/tasks/ingredients_migrator.rb index deb335acb9..9208d810dd 100644 --- a/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +++ b/lib/alchemy/upgrader/tasks/ingredients_migrator.rb @@ -7,7 +7,7 @@ class IngredientsMigrator < Thor include Thor::Actions no_tasks do - def create_ingredients + def create_ingredients(verbose: !Rails.env.test?) Alchemy::Deprecation.silence do elements_with_ingredients = Alchemy::ElementDefinition.all.select { |d| d.key?(:ingredients) } if ENV["ONLY"] @@ -22,13 +22,13 @@ def create_ingredients elements_with_ingredients.map do |element_definition| elements = all_elements.select { |e| e.name == element_definition[:name] } if elements.any? - puts "-- Creating ingredients for #{elements.count} #{element_definition[:name]}(s)" + puts "-- Creating ingredients for #{elements.count} #{element_definition[:name]}(s)" if verbose elements.each do |element| MigrateElementIngredients.call(element) - print "." + print "." if verbose end - puts "\n" - else + puts "\n" if verbose + elsif verbose puts "-- No #{element_definition[:name]} elements found for migration." end end From a014e8e4fb48f2fe852f7ed1cef41145c66d54fd Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Thu, 15 Dec 2022 14:26:08 +0100 Subject: [PATCH 09/10] Fix ingredients upgrader for attributes not known on essence There might be data attributes on ingredient classes, that do not have corosponding attributes on the essence class. --- lib/alchemy/upgrader/tasks/ingredients_migrator.rb | 2 ++ spec/dummy/config/alchemy/elements.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/alchemy/upgrader/tasks/ingredients_migrator.rb b/lib/alchemy/upgrader/tasks/ingredients_migrator.rb index 9208d810dd..1ebf2fafbf 100644 --- a/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +++ b/lib/alchemy/upgrader/tasks/ingredients_migrator.rb @@ -56,6 +56,8 @@ def self.call(element) ingredient.value = content.ingredient end data = ingredient.class.stored_attributes.fetch(:data, []).each_with_object({}) do |attr, d| + next unless essence.respond_to?(attr) + d[attr] = essence.public_send(attr) end ingredient.data = data diff --git a/spec/dummy/config/alchemy/elements.yml b/spec/dummy/config/alchemy/elements.yml index 89b40f8f6a..7477efbcfe 100644 --- a/spec/dummy/config/alchemy/elements.yml +++ b/spec/dummy/config/alchemy/elements.yml @@ -260,6 +260,7 @@ type: Text default: Hello World settings: + anchor: from_value linkable: true - role: text type: Richtext From bf48d42cee8d969ba976fb5653db70599ab85456 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Thu, 15 Dec 2022 09:34:58 +0100 Subject: [PATCH 10/10] Update ingredient icon on element update --- app/views/alchemy/admin/elements/update.js.erb | 3 +++ .../alchemy/admin/ingredients/update.js.erb | 3 +-- package/admin.js | 2 ++ package/src/ingredient_anchor_link.js | 17 +++++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 package/src/ingredient_anchor_link.js diff --git a/app/views/alchemy/admin/elements/update.js.erb b/app/views/alchemy/admin/elements/update.js.erb index c1f1cf8322..f44af7365e 100644 --- a/app/views/alchemy/admin/elements/update.js.erb +++ b/app/views/alchemy/admin/elements/update.js.erb @@ -7,6 +7,9 @@ $errors.hide(); $el.trigger('SaveElement.Alchemy', {previewText: '<%= j sanitize(@element.preview_text) %>'}); + <% @element.ingredients.select { |i| i.settings[:anchor] }.each do |ingredient| %> + Alchemy.IngredientAnchorLink.updateIcon(<%= ingredient.id %>, <%= ingredient.dom_id.present? %>); + <% end %> Alchemy.growl('<%= Alchemy.t(:element_saved) %>'); Alchemy.PreviewWindow.refresh(function() { Alchemy.ElementEditors.focusElementPreview(<%= @element.id %>); diff --git a/app/views/alchemy/admin/ingredients/update.js.erb b/app/views/alchemy/admin/ingredients/update.js.erb index 42d50774e2..ef60ede109 100644 --- a/app/views/alchemy/admin/ingredients/update.js.erb +++ b/app/views/alchemy/admin/ingredients/update.js.erb @@ -1,8 +1,7 @@ Alchemy.closeCurrentDialog( <% if @ingredient.settings[:anchor] %> function() { - const ingredientEditor = document.querySelector('[data-ingredient-id="<%= @ingredient.id %>"]'); - ingredientEditor.querySelector('.edit-ingredient-anchor-link a').innerHTML = '<%= j render_icon(:bookmark, { style: @ingredient.dom_id.present? ? "solid" : "regular" }) %>'; + Alchemy.IngredientAnchorLink.updateIcon(<%= @ingredient.id %>, <%= @ingredient.dom_id.present? %>); } <% end %> ); diff --git a/package/admin.js b/package/admin.js index 2529e74c61..2898d4cee2 100644 --- a/package/admin.js +++ b/package/admin.js @@ -2,6 +2,7 @@ import translate from "./src/i18n" import translationData from "./src/translations" import NodeTree from "./src/node_tree" import fileEditors from "./src/file_editors" +import IngredientAnchorLink from "./src/ingredient_anchor_link" import pictureEditors from "./src/picture_editors" import ImageLoader from "./src/image_loader" import ImageCropper from "./src/image_cropper" @@ -24,6 +25,7 @@ Object.assign(Alchemy, { pictureEditors, ImageLoader: ImageLoader.init, ImageCropper, + IngredientAnchorLink, Datepicker, Sitemap, PagePublicationFields diff --git a/package/src/ingredient_anchor_link.js b/package/src/ingredient_anchor_link.js new file mode 100644 index 0000000000..3af84e1f92 --- /dev/null +++ b/package/src/ingredient_anchor_link.js @@ -0,0 +1,17 @@ +export default class IngredientAnchorLink { + static updateIcon(ingredientId, active = false) { + const ingredientEditor = document.querySelector( + `[data-ingredient-id="${ingredientId}"]` + ) + if (ingredientEditor) { + const icon = ingredientEditor.querySelector( + ".edit-ingredient-anchor-link > a > .icon" + ) + if (icon) { + active + ? icon.classList.replace("far", "fas") + : icon.classList.replace("fas", "far") + } + } + } +}