From 2eb0d79394371e6f6f718c15c865be3a024e0d7f Mon Sep 17 00:00:00 2001 From: DPN MW Date: Sat, 7 Mar 2026 19:36:32 -0400 Subject: [PATCH] Admin Tab Reordering --- .../community-landing-admin-tabs.js | 116 +++++++++++++----- .../stylesheets/community_landing/admin.css | 69 +++++++++-- config/locales/en.yml | 2 +- 3 files changed, 145 insertions(+), 42 deletions(-) diff --git a/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js b/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js index 81a87f9..c122b3b 100644 --- a/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js +++ b/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js @@ -2,13 +2,14 @@ import { withPluginApi } from "discourse/lib/plugin-api"; const TABS = [ { - id: "general", - label: "General", + id: "settings", + label: "Settings", settings: new Set([ "community_landing_enabled", "logo_dark_url", "logo_light_url", "logo_height", "footer_logo_url", "accent_color", "accent_hover_color", "dark_bg_color", "light_bg_color", - "scroll_animation" + "scroll_animation", "staggered_reveal_enabled", "dynamic_background_enabled", + "mouse_parallax_enabled", "scroll_progress_enabled" ]) }, { @@ -26,6 +27,7 @@ const TABS = [ "hero_image_urls", "hero_image_max_height", "hero_primary_button_label", "hero_primary_button_url", "hero_secondary_button_label", "hero_secondary_button_url", + "hero_video_url", "hero_bg_dark", "hero_bg_light", "hero_min_height", "hero_border_style" ]) }, @@ -54,7 +56,7 @@ const TABS = [ id: "topics", label: "Trending", settings: new Set([ - "topics_enabled", "topics_title", "topics_count", + "topics_enabled", "topics_title", "topics_count", "topics_card_bg_color", "topics_bg_dark", "topics_bg_light", "topics_min_height", "topics_border_style" ]) }, @@ -97,7 +99,7 @@ const TABS = [ } ]; -let currentTab = "general"; +let currentTab = "settings"; let filterActive = false; let isActive = false; let recheckTimer = null; @@ -124,9 +126,30 @@ function applyTabFilter() { ); }); - const tabBar = container.querySelector(".cl-admin-tabs"); - if (tabBar) { - tabBar.classList.toggle("filter-active", filterActive); + // Update filter-active dimming on whichever tab container exists + const nativeTabs = container.querySelector(".admin-plugin-config-area__tabs"); + if (nativeTabs) { + nativeTabs.classList.toggle("cl-filter-active", filterActive); + } + const standaloneTabs = container.querySelector(".cl-admin-tabs"); + if (standaloneTabs) { + standaloneTabs.classList.toggle("filter-active", filterActive); + } +} + +function updateActiveStates(activeId) { + const container = getContainer(); + if (!container) return; + + // Update all our injected tabs + container.querySelectorAll(".cl-admin-tab").forEach((btn) => { + btn.classList.toggle("active", btn.dataset.tab === activeId); + }); + + // Update native Settings link if present + const nativeLink = container.querySelector(".cl-native-settings-link"); + if (nativeLink) { + nativeLink.classList.toggle("active", activeId === "settings"); } } @@ -141,12 +164,27 @@ function findFilterInput(container) { return null; } +function handleTabClick(container, tabId) { + currentTab = tabId; + filterActive = false; + + // Clear Discourse filter input so it doesn't conflict + const fi = findFilterInput(container); + if (fi && fi.value) { + fi.value = ""; + fi.dispatchEvent(new Event("input", { bubbles: true })); + } + + updateActiveStates(tabId); + applyTabFilter(); +} + function buildTabsUI() { const container = getContainer(); if (!container) return false; // Already injected — just re-apply filter - if (container.querySelector(".cl-admin-tabs")) { + if (container.querySelector(".cl-admin-tab")) { applyTabFilter(); return true; } @@ -160,7 +198,38 @@ function buildTabsUI() { ); if (!hasOurs) return false; - // Build tab bar + // ── Strategy 1: Inject into native Discourse tab bar ── + const nativeTabsEl = container.querySelector(".admin-plugin-config-area__tabs"); + if (nativeTabsEl) { + // Find the native "Settings" link and hook into it + const nativeLink = nativeTabsEl.querySelector("a"); + if (nativeLink) { + nativeLink.classList.add("cl-native-settings-link", "active"); + nativeLink.addEventListener("click", (e) => { + e.preventDefault(); + handleTabClick(container, "settings"); + }); + } + + // Inject our section tabs into the native bar (skip "settings" — native link handles that) + TABS.forEach((tab) => { + if (tab.id === "settings") return; + + const btn = document.createElement("button"); + btn.className = "cl-admin-tab"; + btn.textContent = tab.label; + btn.dataset.tab = tab.id; + btn.addEventListener("click", () => handleTabClick(container, tab.id)); + nativeTabsEl.appendChild(btn); + }); + + nativeTabsEl.classList.add("cl-tabs-injected"); + container.classList.add("cl-tabs-active"); + applyTabFilter(); + return true; + } + + // ── Strategy 2 (fallback): Standalone tab bar for older Discourse ── const tabBar = document.createElement("div"); tabBar.className = "cl-admin-tabs"; @@ -169,31 +238,12 @@ function buildTabsUI() { btn.className = "cl-admin-tab" + (tab.id === currentTab ? " active" : ""); btn.textContent = tab.label; btn.dataset.tab = tab.id; - btn.addEventListener("click", () => { - currentTab = tab.id; - filterActive = false; - - // Clear Discourse filter input so it doesn't conflict - const fi = findFilterInput(container); - if (fi && fi.value) { - fi.value = ""; - fi.dispatchEvent(new Event("input", { bubbles: true })); - } - - tabBar - .querySelectorAll(".cl-admin-tab") - .forEach((b) => b.classList.remove("active")); - btn.classList.add("active"); - applyTabFilter(); - }); + btn.addEventListener("click", () => handleTabClick(container, tab.id)); tabBar.appendChild(btn); }); - // ── Insertion strategy: place tabs as high as possible ── - let inserted = false; - // Strategy 1: Top of .admin-plugin-config-area__content (above filter bar) const contentArea = container.querySelector( ".admin-plugin-config-area__content" ); @@ -204,7 +254,6 @@ function buildTabsUI() { inserted = true; } - // Strategy 2: Before the filter controls if (!inserted) { const filterArea = container.querySelector( ".admin-site-settings-filter-controls, .setting-filter" @@ -215,7 +264,6 @@ function buildTabsUI() { } } - // Strategy 3: Before the first setting row (fallback) if (!inserted) { allRows[0].parentNode.insertBefore(tabBar, allRows[0]); } @@ -269,7 +317,7 @@ export default { }; tryInject(); - // Periodic re-check: re-injects tab bar if Discourse re-renders the DOM + // Periodic re-check: re-injects tabs if Discourse re-renders the DOM if (!recheckTimer) { recheckTimer = setInterval(() => { if (!isActive) { @@ -278,7 +326,7 @@ export default { return; } const c = getContainer(); - if (c && !c.querySelector(".cl-admin-tabs")) { + if (c && !c.querySelector(".cl-admin-tab")) { buildTabsUI(); } }, 500); diff --git a/assets/stylesheets/community_landing/admin.css b/assets/stylesheets/community_landing/admin.css index 4843fa4..9e1316c 100644 --- a/assets/stylesheets/community_landing/admin.css +++ b/assets/stylesheets/community_landing/admin.css @@ -9,7 +9,64 @@ display: none !important; } -/* ── Tab Navigation ── */ +/* ── Injected tabs inside native Discourse tab bar ── */ + +.admin-plugin-config-area__tabs.cl-tabs-injected { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0; +} + +.admin-plugin-config-area__tabs.cl-tabs-injected .cl-admin-tab { + padding: 10px 14px; + border: none; + background: none; + color: var(--primary-medium, #888); + font-size: 0.875em; + font-weight: 600; + cursor: pointer; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + transition: color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease; +} + +.admin-plugin-config-area__tabs.cl-tabs-injected .cl-admin-tab:hover { + color: var(--primary, #333); +} + +.admin-plugin-config-area__tabs.cl-tabs-injected .cl-admin-tab.active { + color: var(--tertiary, #0088cc); + border-bottom-color: var(--tertiary, #0088cc); +} + +/* Native "Settings" link — match our tab styling when active/inactive */ +.admin-plugin-config-area__tabs.cl-tabs-injected .cl-native-settings-link { + transition: color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease; +} + +.admin-plugin-config-area__tabs.cl-tabs-injected .cl-native-settings-link:not(.active) { + color: var(--primary-medium, #888); + border-bottom-color: transparent; +} + +/* Dimmed state when Discourse filter/search is active */ +.admin-plugin-config-area__tabs.cl-filter-active .cl-admin-tab, +.admin-plugin-config-area__tabs.cl-filter-active .cl-native-settings-link { + opacity: 0.4; +} + +.admin-plugin-config-area__tabs.cl-filter-active .cl-admin-tab.active, +.admin-plugin-config-area__tabs.cl-filter-active .cl-native-settings-link.active { + border-bottom-color: transparent; +} + +/* Dark mode */ +html.dark-scheme .admin-plugin-config-area__tabs.cl-tabs-injected .cl-admin-tab:hover { + color: var(--primary, #ddd); +} + +/* ── Standalone tab bar (fallback for older Discourse without native tabs) ── */ .cl-admin-tabs { display: flex; @@ -20,7 +77,7 @@ border-bottom: 1px solid var(--primary-low, rgba(0, 0, 0, 0.1)); } -.cl-admin-tab { +.cl-admin-tabs .cl-admin-tab { padding: 10px 14px; border: none; background: none; @@ -33,16 +90,15 @@ transition: color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease; } -.cl-admin-tab:hover { +.cl-admin-tabs .cl-admin-tab:hover { color: var(--primary, #333); } -.cl-admin-tab.active { +.cl-admin-tabs .cl-admin-tab.active { color: var(--tertiary, #0088cc); border-bottom-color: var(--tertiary, #0088cc); } -/* Dimmed state when Discourse filter/search is active */ .cl-admin-tabs.filter-active .cl-admin-tab { opacity: 0.4; } @@ -51,12 +107,11 @@ border-bottom-color: transparent; } -/* Dark mode tabs */ html.dark-scheme .cl-admin-tabs { border-bottom-color: rgba(255, 255, 255, 0.1); } -html.dark-scheme .cl-admin-tab:hover { +html.dark-scheme .cl-admin-tabs .cl-admin-tab:hover { color: var(--primary, #ddd); } diff --git a/config/locales/en.yml b/config/locales/en.yml index 48b583f..0f5fea2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,7 +1,7 @@ en: site_settings: # ── Master Switch ── - community_landing_enabled: "Enable the branded community landing page. When on, logged-out visitors see a custom welcome page instead of the default Discourse homepage. All sections below are configurable." + community_landing_enabled: "Enable the community landing page." # ── Branding: Logo ── logo_dark_url: "━━ BRANDING ━━ — Logo image URL for dark mode. Displayed in the navbar and footer. Leave blank to show the site name as text."