+
+
<%- end -%>
diff --git a/app/views/layouts/alchemy/admin.html.erb b/app/views/layouts/alchemy/admin.html.erb
index 8c90c5fba8..b836880973 100644
--- a/app/views/layouts/alchemy/admin.html.erb
+++ b/app/views/layouts/alchemy/admin.html.erb
@@ -5,6 +5,7 @@
<%= render_alchemy_title %>
+
<%= csrf_meta_tag %>
<%= stylesheet_link_tag('alchemy/admin/all', media: 'screen', 'data-turbo-track' => true) %>
diff --git a/config/importmap.rb b/config/importmap.rb
index 3e74c3902f..319e4cc656 100644
--- a/config/importmap.rb
+++ b/config/importmap.rb
@@ -3,6 +3,9 @@
pin "lodash-es/max", to: "https://ga.jspm.io/npm:lodash-es@4.17.21/max.js", preload: true
pin "sortablejs", to: "https://ga.jspm.io/npm:sortablejs@1.15.0/modular/sortable.esm.js", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
+pin "@shoelace/tab", to: "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.9.0/cdn/components/tab/tab.js", preload: true
+pin "@shoelace/tab-group", to: "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.9.0/cdn/components/tab-group/tab-group.js", preload: true
+pin "@shoelace/tab-panel", to: "https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.9.0/cdn/components/tab-panel/tab-panel.js", preload: true
pin "alchemy_admin", to: "alchemy_admin.js", preload: true
pin_all_from File.expand_path("../app/javascript/alchemy_admin", __dir__), under: "alchemy_admin"
diff --git a/spec/features/admin/edit_elements_feature_spec.rb b/spec/features/admin/edit_elements_feature_spec.rb
index e809544575..ef81472ce2 100644
--- a/spec/features/admin/edit_elements_feature_spec.rb
+++ b/spec/features/admin/edit_elements_feature_spec.rb
@@ -68,7 +68,7 @@
expect(button).to have_content "Add slide"
button.click
expect(page).to have_select("Element")
- expect(page).to have_link("Paste from clipboard")
+ expect(page).to have_css("[panel='paste_element_tab']")
end
end
@@ -93,7 +93,7 @@
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")
+ expect(page).to have_css("[panel='paste_element_tab']")
end
end
end
diff --git a/spec/features/admin/legacy_page_url_management_spec.rb b/spec/features/admin/legacy_page_url_management_spec.rb
index 735efa9fbd..2529458e0c 100644
--- a/spec/features/admin/legacy_page_url_management_spec.rb
+++ b/spec/features/admin/legacy_page_url_management_spec.rb
@@ -17,7 +17,7 @@ def open_page_properties
it "lets a user add a page link" do
open_page_properties
- click_link "Links"
+ find("[panel='legacy_urls']").click
fill_in "legacy_page_url_urlname", with: "new-urlname"
click_button "Add"
within "#legacy_page_urls" do
@@ -31,7 +31,7 @@ def open_page_properties
context "with wrong url format" do
it "displays error message" do
open_page_properties
- click_link "Links"
+ find("[panel='legacy_urls']").click
fill_in "legacy_page_url_urlname", with: "invalid url name"
click_button "Add"
within "#new_legacy_page_url" do
@@ -44,7 +44,7 @@ def open_page_properties
before do
a_page.legacy_urls.create!(urlname: "a-page-link")
open_page_properties
- click_link "(1) Link"
+ find("[panel='legacy_urls']").click
end
it "lets a user remove a page link" do
diff --git a/spec/features/admin/link_overlay_spec.rb b/spec/features/admin/link_overlay_spec.rb
index d358c9195b..6a470bc5db 100644
--- a/spec/features/admin/link_overlay_spec.rb
+++ b/spec/features/admin/link_overlay_spec.rb
@@ -61,7 +61,7 @@
click_link "Link text"
end
- within "#overlay_tab_internal_link" do
+ within "[name='overlay_tab_internal_link']" do
expect(page).to have_selector("#s2id_internal_link")
select2_search(page2.name, from: "Page")
click_button "apply"
@@ -90,10 +90,10 @@
end
within "#overlay_tabs" do
- click_link "External"
+ find("[panel='overlay_tab_external_link']").click
end
- within "#overlay_tab_external_link" do
+ within "[name='overlay_tab_external_link']" do
expect(page).to have_selector("#external_link")
fill_in("URL", with: "https://example.com")
click_button "apply"
@@ -123,10 +123,10 @@
end
within "#overlay_tabs" do
- click_link "File"
+ find("[panel='overlay_tab_file_link']").click
end
- within "#overlay_tab_file_link" do
+ within "[name='overlay_tab_file_link']" do
expect(page).to have_selector("#file_link")
select2(file.name, from: "File")
click_button "apply"
diff --git a/spec/features/admin/page_creation_feature_spec.rb b/spec/features/admin/page_creation_feature_spec.rb
index cf21a3f4af..c7d1e92273 100644
--- a/spec/features/admin/page_creation_feature_spec.rb
+++ b/spec/features/admin/page_creation_feature_spec.rb
@@ -39,7 +39,7 @@
it "contains tabs for creating a new page and pasting from clipboard" do
visit new_admin_page_path
- within("#overlay_tabs") { expect(page).to have_selector "#create_page_tab, #paste_page_tab" }
+ within("#overlay_tabs") { expect(page).to have_selector "[panel='create_page_tab'], [panel='paste_page_tab']" }
end
context "", js: true do
@@ -52,17 +52,17 @@
it "the create page tab is visible by default" do
within("#overlay_tabs") do
- expect(page).to have_selector("#create_page_tab", visible: true)
- expect(page).to have_selector("#paste_page_tab", visible: false)
+ expect(page).to have_selector("[panel='create_page_tab']", visible: true)
+ expect(page).to have_selector("[panel='paste_page_tab']", visible: false)
end
end
context "when clicking on an inactive tab" do
it "shows that clicked tab" do
within("#overlay_tabs") do
- click_link("Paste from clipboard")
- expect(find("#create_page_tab")).to_not be_visible
- expect(find("#paste_page_tab")).to be_visible
+ find("[panel='paste_page_tab']").click
+ expect(find("[name='create_page_tab']")).to_not be_visible
+ expect(find("[name='paste_page_tab']")).to be_visible
end
end
end
diff --git a/vendor/assets/javascripts/jquery_plugins/jquery.ui.tabspaging.js b/vendor/assets/javascripts/jquery_plugins/jquery.ui.tabspaging.js
deleted file mode 100644
index 8d9a4972f7..0000000000
--- a/vendor/assets/javascripts/jquery_plugins/jquery.ui.tabspaging.js
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * UI Tabs Paging extension - v1.2.2 (for jQuery 1.9.0 and jQuery UI 1.9.0)
- *
- * Copyright (c) 2013, http://seyfertdesign.com/jquery/ui-tabs-paging.html
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * Depends:
- * jquery.ui.core.js
- * jquery.ui.widget.js
- * jquery.ui.tabs.js
- */
-
-(function($) {
-
-// overridden ui.tabs functions
-var uiTabsFuncs = {
- refresh: $.ui.tabs.prototype.refresh,
- option: $.ui.tabs.prototype.option
-};
-
-$.extend($.ui.tabs.prototype, {
- paging: function(options) {
- var opts = {
- tabsPerPage: 0, // Max number of tabs to display at one time. 0 automatically sizing.
- nextButton: '»', // Text displayed for next button.
- prevButton: '«', // Text displayed for previous button.
- follow: false, // When clicking next button, automatically make first tab active. When clicking previous button automatically make last tab active.
- cycle: false, // When at end of list, next button returns to first page. When at beginning of list previous button goes to end of list.
- activeOnAdd: false, // When new tab is added, make tab active automatically
- followOnActive: false // When tab is changed to active, automatically go move to that tab group.
- };
-
- opts = $.extend(opts, options);
-
- var self = this, initialized = false, currentPage,
- buttonWidth, containerWidth, allTabsWidth, tabWidths,
- maxPageWidth, pages, resizeTimer = null,
- windowHeight, windowWidth;
-
- // initialize paging
- function init() {
- destroy();
-
- windowHeight = $(window).height();
- windowWidth = $(window).width();
-
- allTabsWidth = 0, currentPage = 0, maxPageWidth = 0, buttonWidth = 0,
- pages = new Array(), tabWidths = new Array(), selectedTabWidths = new Array();
-
- containerWidth = self.element.width();
-
- // loops through LIs, get width of each tab when selected and unselected.
- var maxDiff = 0; // the max difference between a selected and unselected tab
- self.tabs.each(function(i) {
- if (i == self.options.active) {
- selectedTabWidths[i] = $(this).outerWidth(true);
- tabWidths[i] = self.tabs.eq(i).removeClass('ui-tabs-active').outerWidth(true);
- self.tabs.eq(i).addClass('ui-tabs-active');
- maxDiff = Math.min(maxDiff, Math.abs(selectedTabWidths[i] - tabWidths[i]));
- allTabsWidth += tabWidths[i];
- } else {
- tabWidths[i] = $(this).outerWidth(true);
- selectedTabWidths[i] = self.tabs.eq(i).addClass('ui-tabs-active').outerWidth(true);
- self.tabs.eq(i).removeClass('ui-tabs-active');
- maxDiff = Math.max(maxDiff, Math.abs(selectedTabWidths[i] - tabWidths[i]));
- allTabsWidth += tabWidths[i];
- }
- });
-
- // fix padding issues with buttons
- // TODO determine a better way to handle this
- allTabsWidth += maxDiff + 9;
-
- // if the width of all tables is greater than the container's width, calculate the pages
- if (allTabsWidth > containerWidth) {
- // create next button
- li = $('')
- .addClass('ui-state-default ui-tabs-paging-next')
- .append($('')
- .click(function() { page('next'); return false; })
- .html(opts.nextButton));
-
- self.tablist.append(li);
- buttonWidth = li.outerWidth(true);
-
- // create prev button
- li = $('')
- .addClass('ui-state-default ui-tabs-paging-prev')
- .append($('')
- .click(function() { page('prev'); return false; })
- .html(opts.prevButton));
- self.tablist.prepend(li);
- buttonWidth += li.outerWidth(true);
-
- // TODO determine fix for padding issues to next button
- buttonWidth += 19;
-
- var pageIndex = 0, pageWidth = 0, maxTabPadding = 0;
-
- // start calculating pageWidths
- for (var i = 0; i < tabWidths.length; i++) {
- // if first tab of page or selected tab's padding larger than the current max, set the maxTabPadding
- if (pageWidth == 0 || selectedTabWidths[i] - tabWidths[i] > maxTabPadding)
- maxTabPadding = (selectedTabWidths[i] - tabWidths[i]);
-
- // if first tab of page, initialize pages variable for page
- if (pages[pageIndex] == null) {
- pages[pageIndex] = { start: i };
-
- } else if ((i > 0 && (i % opts.tabsPerPage) == 0) || (tabWidths[i] + pageWidth + buttonWidth + 12) > containerWidth) {
- if ((pageWidth + maxTabPadding) > maxPageWidth)
- maxPageWidth = (pageWidth + maxTabPadding);
- pageIndex++;
- pages[pageIndex] = { start: i };
- pageWidth = 0;
- }
- pages[pageIndex].end = i+1;
- pageWidth += tabWidths[i];
- if (i == self.options.active) currentPage = pageIndex;
- }
- if ((pageWidth + maxTabPadding) > maxPageWidth)
- maxPageWidth = (pageWidth + maxTabPadding);
-
- // hide all tabs then show tabs for current page
- self.tabs.hide().slice(pages[currentPage].start, pages[currentPage].end).show();
- if (currentPage == (pages.length - 1) && !opts.cycle)
- disableButton('next');
- if (currentPage == 0 && !opts.cycle)
- disableButton('prev');
-
- // calculate the right padding for the next button
- buttonPadding = containerWidth - maxPageWidth - buttonWidth;
- if (buttonPadding > 0)
- $('.ui-tabs-paging-next', self.element).css({ paddingRight: buttonPadding + 'px' });
- } else {
- destroy();
- }
-
- $(window).bind('resize', handleResize);
-
- initialized = true;
- }
-
- // handles paging forward and backward
- function page(direction) {
- currentPage = currentPage + (direction == 'prev'?-1:1);
-
- if ((direction == 'prev' && currentPage < 0 && opts.cycle) ||
- (direction == 'next' && currentPage >= pages.length && !opts.cycle))
- currentPage = pages.length - 1;
- else if ((direction == 'prev' && currentPage < 0) ||
- (direction == 'next' && currentPage >= pages.length && opts.cycle))
- currentPage = 0;
-
- var start = pages[currentPage].start;
- var end = pages[currentPage].end;
- self.tabs.hide().slice(start, end).show();
-
- if (direction == 'prev') {
- enableButton('next');
- if (opts.follow && (self.options.active < start || self.options.active > (end-1))) self.option('active', end-1);
- if (!opts.cycle && start <= 0) disableButton('prev');
- } else {
- enableButton('prev');
- if (opts.follow && (self.options.active < start || self.options.active > (end-1))) self.option('active', start);
- if (!opts.cycle && end >= self.tabs.length) disableButton('next');
- }
- }
-
- // change styling of next/prev buttons when disabled
- function disableButton(direction) {
- $('.ui-tabs-paging-'+direction, self.element).addClass('ui-tabs-paging-disabled');
- }
-
- function enableButton(direction) {
- $('.ui-tabs-paging-'+direction, self.element).removeClass('ui-tabs-paging-disabled');
- }
-
- // special function defined to handle IE resize issues
- function handleResize() {
- if (resizeTimer) clearTimeout(resizeTimer);
-
- if (windowHeight != $(window).height() || windowWidth != $(window).width())
- {
- resizeTimer = setTimeout(init, 100);
- }
- }
-
- // remove all paging related changes and events
- function destroy() {
- // remove buttons
- $('.ui-tabs-paging-next', self.element).remove();
- $('.ui-tabs-paging-prev', self.element).remove();
-
- // show all tabs
- self.tabs.show();
-
- initialized = false;
-
- $(window).unbind('resize', handleResize);
- }
-
-
-
- // ------------- OVERRIDDEN PUBLIC FUNCTIONS -------------
- self.option = function(optionName, value) {
- var retVal = uiTabsFuncs.option.apply(this, [optionName, value]);
-
- // if "followOnActive" is true, then move page when selection changes
- if (optionName == "active")
- {
- // if paging is not initialized or it is not configured to
- // change pages when a new tab is active, then do nothing
- if (!initialized || !opts.followOnActive)
- return retVal;
-
- // find the new page based on index of the active tab
- for (var i in pages) {
- var start = pages[i].start;
- var end = pages[i].end;
- if (value >= start && value < end) {
- // if the the active tab is not within the currentPage of tabs, then change pages
- if (i != currentPage) {
- this.tabs.hide().slice(start, end).show();
-
- currentPage = parseInt(i);
- if (currentPage == 0) {
- enableButton('next');
- if (!opts.cycle && start <= 0) disableButton('prev');
- } else {
- enableButton('prev');
- if (!opts.cycle && end >= this.tabs.length) disableButton('next');
- }
- }
- break;
- }
- }
- }
-
- return retVal;
- }
-
- self.refresh = function() {
- if (initialized)
- {
- destroy();
-
- uiTabsFuncs.refresh.apply(this);
-
- // re-initialize paging buttons
- init();
- }
-
- uiTabsFuncs.refresh.apply(this);
- }
-
-
- // ------------- PUBLIC FUNCTIONS -------------
- $.extend($.ui.tabs.prototype, {
- // public function for removing paging
- pagingDestroy: function() {
- destroy();
- return this;
- },
-
- // public function to handle resizes that are not on the window
- pagingResize: function() {
- init();
- return this;
- }
- });
-
- // initialize on startup!
- init();
- }
-});
-
-
-})(jQuery);