diff --git a/app/components/alchemy/admin/link_dialog/anchor_tab.rb b/app/components/alchemy/admin/link_dialog/anchor_tab.rb
new file mode 100644
index 0000000000..6ed0bec6dc
--- /dev/null
+++ b/app/components/alchemy/admin/link_dialog/anchor_tab.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Alchemy
+ module Admin
+ module LinkDialog
+ class AnchorTab < BaseTab
+ def title
+ Alchemy.t("link_overlay_tab_label.anchor")
+ end
+
+ def name
+ :anchor
+ end
+
+ def fields
+ [
+ anchor_select,
+ title_input
+ ]
+ end
+
+ def message
+ render_message(:info, content_tag("p", Alchemy.t(:anchor_link_headline)))
+ end
+
+ private
+
+ def anchor_select
+ label = label_tag("anchor_link", Alchemy.t(:anchor), class: "control-label")
+ select = select_tag(:anchor_link,
+ options_for_select([[Alchemy.t("Please choose"), ""]]),
+ is: "alchemy-select")
+ content_tag("div", label + select, class: "input select")
+ end
+ end
+ end
+ end
+end
diff --git a/app/components/alchemy/admin/link_dialog/base_tab.rb b/app/components/alchemy/admin/link_dialog/base_tab.rb
new file mode 100644
index 0000000000..132bc54e07
--- /dev/null
+++ b/app/components/alchemy/admin/link_dialog/base_tab.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Alchemy
+ module Admin
+ module LinkDialog
+ class BaseTab < ViewComponent::Base
+ include BaseHelper
+
+ erb_template <<~ERB
+ <%= title %>
+
+
+
+ ERB
+
+ def title
+ raise ArgumentError, "The tab needs to have a title"
+ end
+
+ def name
+ raise ArgumentError, "The tab needs to have a name"
+ end
+
+ def fields
+ []
+ end
+
+ def message
+ ""
+ end
+
+ private
+
+ def panel_name
+ "overlay_tab_#{name}_link"
+ end
+
+ def title_input
+ input_name = "#{name}_link_title"
+ label = label_tag(input_name, Alchemy.t(:link_title), class: "control-label")
+ input = text_field_tag input_name, "", class: "link_title"
+ content_tag("div", label + input, class: "input text")
+ end
+
+ def target_select
+ select_name = "#{name}_link_target"
+ label = label_tag(select_name, Alchemy.t("Open Link in"), class: "control-label")
+ select = select_tag(select_name, options_for_select(Alchemy::Page.link_target_options, @target), class: "link_target")
+ content_tag("div", label + select, class: "input select")
+ end
+ end
+ end
+ end
+end
diff --git a/app/components/alchemy/admin/link_dialog/external_tab.rb b/app/components/alchemy/admin/link_dialog/external_tab.rb
new file mode 100644
index 0000000000..90fac119da
--- /dev/null
+++ b/app/components/alchemy/admin/link_dialog/external_tab.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Alchemy
+ module Admin
+ module LinkDialog
+ class ExternalTab < BaseTab
+ def title
+ Alchemy.t("link_overlay_tab_label.external")
+ end
+
+ def name
+ :external
+ end
+
+ def fields
+ [
+ url_input,
+ title_input,
+ target_select
+ ]
+ end
+
+ def message
+ main_message = content_tag("h3", Alchemy.t(:enter_external_link)) +
+ content_tag("p", Alchemy.t(:external_link_notice_1)) +
+ content_tag("p", Alchemy.t(:external_link_notice_2))
+
+ render_message(:info, main_message) +
+ content_tag("div", content_tag("ul"), id: "errors", class: "errors")
+ end
+
+ private
+
+ def url_input
+ label = label_tag("external_link", "URL", class: "control-label")
+ input = text_field_tag "external_link", ""
+ content_tag("div", label + input, class: "input text")
+ end
+ end
+ end
+ end
+end
diff --git a/app/components/alchemy/admin/link_dialog/file_tab.rb b/app/components/alchemy/admin/link_dialog/file_tab.rb
new file mode 100644
index 0000000000..abd6cf12ba
--- /dev/null
+++ b/app/components/alchemy/admin/link_dialog/file_tab.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Alchemy
+ module Admin
+ module LinkDialog
+ class FileTab < BaseTab
+ delegate :alchemy, to: :helpers
+
+ def title
+ Alchemy.t("link_overlay_tab_label.file")
+ end
+
+ def name
+ :file
+ end
+
+ def fields
+ [
+ attachment_select,
+ title_input,
+ target_select
+ ]
+ end
+
+ def message
+ render_message(:info, content_tag("h3", Alchemy.t(:choose_file_to_link)))
+ end
+
+ private
+
+ def attachments
+ @_attachments ||= Attachment.all.collect { |f|
+ [f.name, alchemy.download_attachment_path(id: f.id, name: f.slug)]
+ }
+ end
+
+ def attachment_select
+ label = label_tag("file_link", Alchemy.t(:file), class: "control-label")
+ select = select_tag "file_link",
+ options_for_select(attachments),
+ prompt: Alchemy.t("Please choose"),
+ is: "alchemy-select"
+ content_tag("div", label + select, class: "input select")
+ end
+ end
+ end
+ end
+end
diff --git a/app/components/alchemy/admin/link_dialog/internal_tab.rb b/app/components/alchemy/admin/link_dialog/internal_tab.rb
new file mode 100644
index 0000000000..9e6865155f
--- /dev/null
+++ b/app/components/alchemy/admin/link_dialog/internal_tab.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Alchemy
+ module Admin
+ module LinkDialog
+ class InternalTab < BaseTab
+ def title
+ Alchemy.t("link_overlay_tab_label.internal")
+ end
+
+ def name
+ :internal
+ end
+
+ def fields
+ [
+ page_select,
+ dom_id_select,
+ title_input,
+ target_select
+ ]
+ end
+
+ def message
+ main_message = content_tag("h3", Alchemy.t(:internal_link_headline)) +
+ content_tag("p", Alchemy.t(:internal_link_page_elements_explanation))
+ render_message(:info, main_message)
+ end
+
+ private
+
+ def page_select
+ label = label_tag("internal_link", Alchemy.t(:page), class: "control-label")
+ input = text_field_tag("internal_link", "", id: "internal_link", class: "alchemy_selectbox full_width")
+ content_tag("div", label + input, class: "input select")
+ end
+
+ def dom_id_select
+ label = label_tag("element_anchor", Alchemy.t(:anchor), class: "control-label")
+ options = {id: "element_anchor", class: "alchemy_selectbox full_width", disabled: true, placeholder: Alchemy.t("Select a page first")}
+ input = text_field_tag("element_anchor", nil, options)
+ content_tag("div", label + input, class: "input select")
+ end
+ end
+ end
+ end
+end
diff --git a/app/components/alchemy/admin/link_dialog/tabs.rb b/app/components/alchemy/admin/link_dialog/tabs.rb
new file mode 100644
index 0000000000..8554e4c9e1
--- /dev/null
+++ b/app/components/alchemy/admin/link_dialog/tabs.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Alchemy
+ module Admin
+ module LinkDialog
+ class Tabs < ViewComponent::Base
+ erb_template <<~ERB
+
+ <% tabs.each do |tab| %>
+ <%= render tab.new %>
+ <% end %>
+
+ ERB
+
+ def tabs
+ Alchemy.link_dialog_tabs
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/alchemy/admin/pages_controller.rb b/app/controllers/alchemy/admin/pages_controller.rb
index 1cea2287f1..4b37425793 100644
--- a/app/controllers/alchemy/admin/pages_controller.rb
+++ b/app/controllers/alchemy/admin/pages_controller.rb
@@ -163,9 +163,7 @@ def destroy
end
def link
- @attachments = Attachment.all.collect { |f|
- [f.name, download_attachment_path(id: f.id, name: f.slug)]
- }
+ render LinkDialog::Tabs.new
end
def fold
diff --git a/app/views/alchemy/admin/pages/_anchor_link.html.erb b/app/views/alchemy/admin/pages/_anchor_link.html.erb
deleted file mode 100644
index 64b44e4e50..0000000000
--- a/app/views/alchemy/admin/pages/_anchor_link.html.erb
+++ /dev/null
@@ -1,22 +0,0 @@
-
diff --git a/app/views/alchemy/admin/pages/_external_link.html.erb b/app/views/alchemy/admin/pages/_external_link.html.erb
deleted file mode 100644
index e6e21653b5..0000000000
--- a/app/views/alchemy/admin/pages/_external_link.html.erb
+++ /dev/null
@@ -1,31 +0,0 @@
-
diff --git a/app/views/alchemy/admin/pages/_file_link.html.erb b/app/views/alchemy/admin/pages/_file_link.html.erb
deleted file mode 100644
index 355d3adaf5..0000000000
--- a/app/views/alchemy/admin/pages/_file_link.html.erb
+++ /dev/null
@@ -1,31 +0,0 @@
-
diff --git a/app/views/alchemy/admin/pages/_internal_link.html.erb b/app/views/alchemy/admin/pages/_internal_link.html.erb
deleted file mode 100644
index 7280ec8f3c..0000000000
--- a/app/views/alchemy/admin/pages/_internal_link.html.erb
+++ /dev/null
@@ -1,35 +0,0 @@
-
diff --git a/app/views/alchemy/admin/pages/link.html.erb b/app/views/alchemy/admin/pages/link.html.erb
deleted file mode 100644
index 733e4a51be..0000000000
--- a/app/views/alchemy/admin/pages/link.html.erb
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
- <%= Alchemy.t('link_overlay_tab_label.internal') %>
-
-
- <%= Alchemy.t('link_overlay_tab_label.anchor') %>
-
-
- <%= Alchemy.t('link_overlay_tab_label.external') %>
-
-
- <%= Alchemy.t('link_overlay_tab_label.file') %>
-
-
- <%= render partial: 'internal_link' %>
-
-
- <%= render partial: 'anchor_link' %>
-
-
- <%= render partial: 'external_link' %>
-
-
- <%= render partial: 'file_link' %>
-
-
diff --git a/lib/alchemy.rb b/lib/alchemy.rb
index b4cc25d568..04435e7129 100644
--- a/lib/alchemy.rb
+++ b/lib/alchemy.rb
@@ -102,4 +102,40 @@ def self.publish_targets
# JS Importmap instance
singleton_class.attr_accessor :importmap
self.importmap = Importmap::Map.new
+
+ # Configure tabs in the link dialog
+ #
+ # With this configuration that tabs in the link dialog can be extended
+ # without overwriting or defacing the Admin Interface.
+ #
+ # == Example
+ #
+ # # components/acme/link_tab.rb
+ # module Acme
+ # class LinkTab < ::Alchemy::Admin::LinkDialog::BaseTab
+ # def title
+ # "Awesome Tab Title"
+ # end
+ #
+ # def name
+ # :unique_name
+ # end
+ #
+ # def fields
+ # [ title_input, target_select ]
+ # end
+ # end
+ # end
+ #
+ # # config/initializers/alchemy.rb
+ # Alchemy.link_dialog_tabs << Acme::LinkTab
+ #
+ def self.link_dialog_tabs
+ @_link_dialog_tabs ||= Set.new([
+ Alchemy::Admin::LinkDialog::InternalTab,
+ Alchemy::Admin::LinkDialog::AnchorTab,
+ Alchemy::Admin::LinkDialog::ExternalTab,
+ Alchemy::Admin::LinkDialog::FileTab
+ ])
+ end
end
diff --git a/spec/components/alchemy/admin/link_dialog/anchor_tab_spec.rb b/spec/components/alchemy/admin/link_dialog/anchor_tab_spec.rb
new file mode 100644
index 0000000000..e39de4a6a6
--- /dev/null
+++ b/spec/components/alchemy/admin/link_dialog/anchor_tab_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+RSpec.describe Alchemy::Admin::LinkDialog::AnchorTab, type: :component do
+ before do
+ render_inline(described_class.new)
+ end
+
+ it "should have an anchor select" do
+ expect(page).to have_selector("select[name=anchor_link] option")
+ end
+
+ it "should have a title input" do
+ expect(page).to have_selector("input[name=anchor_link_title]")
+ end
+end
diff --git a/spec/components/alchemy/admin/link_dialog/base_tab_spec.rb b/spec/components/alchemy/admin/link_dialog/base_tab_spec.rb
new file mode 100644
index 0000000000..6df9271230
--- /dev/null
+++ b/spec/components/alchemy/admin/link_dialog/base_tab_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+class BaseTestTab < Alchemy::Admin::LinkDialog::BaseTab
+ delegate :render_message, to: :helpers
+
+ def title
+ "Base Test Tab"
+ end
+
+ def name
+ :base_test
+ end
+
+ def fields
+ [
+ title_input,
+ target_select
+ ]
+ end
+end
+
+RSpec.describe Alchemy::Admin::LinkDialog::BaseTab, type: :component do
+ before do
+ render_inline(BaseTestTab.new)
+ end
+
+ it "should render a tab with a panel" do
+ expect(page).to have_selector("sl-tab[panel='overlay_tab_base_test_link']")
+ expect(page).to have_selector("sl-tab-panel[name='overlay_tab_base_test_link']")
+ end
+
+ it "should have a title" do
+ expect(page).to have_text("Base Test Tab")
+ end
+
+ it "should allow to add title input" do
+ expect(page).to have_selector("input[name=base_test_link_title]")
+ end
+
+ it "should allow to add target select" do
+ expect(page).to have_selector("select[name=base_test_link_target]")
+ end
+end
diff --git a/spec/components/alchemy/admin/link_dialog/external_tab_spec.rb b/spec/components/alchemy/admin/link_dialog/external_tab_spec.rb
new file mode 100644
index 0000000000..caae666c5a
--- /dev/null
+++ b/spec/components/alchemy/admin/link_dialog/external_tab_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+RSpec.describe Alchemy::Admin::LinkDialog::ExternalTab, type: :component do
+ before do
+ render_inline(described_class.new)
+ end
+
+ it "should have an url input" do
+ expect(page).to have_selector("input[name=external_link]")
+ end
+
+ it "should have a title input" do
+ expect(page).to have_selector("input[name=external_link_title]")
+ end
+
+ it "should have a target select" do
+ expect(page).to have_selector("select[name=external_link_target]")
+ end
+end
diff --git a/spec/components/alchemy/admin/link_dialog/file_tab_spec.rb b/spec/components/alchemy/admin/link_dialog/file_tab_spec.rb
new file mode 100644
index 0000000000..c3c8658ff4
--- /dev/null
+++ b/spec/components/alchemy/admin/link_dialog/file_tab_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+RSpec.describe Alchemy::Admin::LinkDialog::FileTab, type: :component do
+ let!(:attachment) { create(:alchemy_attachment) }
+ let(:url) { Alchemy::Engine.routes.url_helpers.download_attachment_path(id: attachment.id, name: attachment.slug) }
+
+ before do
+ render_inline(described_class.new)
+ end
+
+ it "should render a pre-filled file select" do
+ expect(page.find(:css, "select[name=file_link] option:last-child").value).to eq(url)
+ end
+
+ it "should have a title input" do
+ expect(page).to have_selector("input[name=file_link_title]")
+ end
+
+ it "should have a target select" do
+ expect(page).to have_selector("select[name=file_link_target]")
+ end
+end
diff --git a/spec/components/alchemy/admin/link_dialog/internal_tab_spec.rb b/spec/components/alchemy/admin/link_dialog/internal_tab_spec.rb
new file mode 100644
index 0000000000..2282afcb87
--- /dev/null
+++ b/spec/components/alchemy/admin/link_dialog/internal_tab_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+RSpec.describe Alchemy::Admin::LinkDialog::InternalTab, type: :component do
+ before do
+ render_inline(described_class.new)
+ end
+
+ it "should have an url input" do
+ expect(page).to have_selector("input[name=internal_link]")
+ end
+
+ it "should have a dom id select" do
+ expect(page).to have_selector("input[name=element_anchor]")
+ end
+
+ it "should have a title input" do
+ expect(page).to have_selector("input[name=internal_link_title]")
+ end
+
+ it "should have a target select" do
+ expect(page).to have_selector("select[name=internal_link_target]")
+ end
+end
diff --git a/spec/features/admin/link_overlay_spec.rb b/spec/features/admin/link_overlay_spec.rb
index 42d02d1531..dc52a93325 100644
--- a/spec/features/admin/link_overlay_spec.rb
+++ b/spec/features/admin/link_overlay_spec.rb
@@ -2,6 +2,20 @@
require "rails_helper"
+class TestTab < Alchemy::Admin::LinkDialog::BaseTab
+ def title
+ "Test Tab"
+ end
+
+ def name
+ :test_tab
+ end
+
+ def fields
+ [title_input, target_select]
+ end
+end
+
RSpec.describe "Link overlay", type: :system do
let!(:language) { create(:alchemy_language) }
@@ -29,6 +43,17 @@
visit link_admin_pages_path
within("#overlay_tabs") { expect(page).to have_content("File") }
end
+
+ context "add new tab" do
+ before do
+ Alchemy.link_dialog_tabs << TestTab
+ end
+
+ it "has a new tab" do
+ visit link_admin_pages_path
+ within("#overlay_tabs") { expect(page).to have_content("Test Tab") }
+ end
+ end
end
context "linking pages", js: true do