mirror of
https://github.com/dpnmw/community-landing.git
synced 2026-03-18 09:27:16 +00:00
Admin Tab Reordering
This commit is contained in:
@@ -2,13 +2,14 @@ import { withPluginApi } from "discourse/lib/plugin-api";
|
|||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{
|
{
|
||||||
id: "general",
|
id: "settings",
|
||||||
label: "General",
|
label: "Settings",
|
||||||
settings: new Set([
|
settings: new Set([
|
||||||
"community_landing_enabled",
|
"community_landing_enabled",
|
||||||
"logo_dark_url", "logo_light_url", "logo_height", "footer_logo_url",
|
"logo_dark_url", "logo_light_url", "logo_height", "footer_logo_url",
|
||||||
"accent_color", "accent_hover_color", "dark_bg_color", "light_bg_color",
|
"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_image_urls", "hero_image_max_height",
|
||||||
"hero_primary_button_label", "hero_primary_button_url",
|
"hero_primary_button_label", "hero_primary_button_url",
|
||||||
"hero_secondary_button_label", "hero_secondary_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"
|
"hero_bg_dark", "hero_bg_light", "hero_min_height", "hero_border_style"
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
@@ -54,7 +56,7 @@ const TABS = [
|
|||||||
id: "topics",
|
id: "topics",
|
||||||
label: "Trending",
|
label: "Trending",
|
||||||
settings: new Set([
|
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"
|
"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 filterActive = false;
|
||||||
let isActive = false;
|
let isActive = false;
|
||||||
let recheckTimer = null;
|
let recheckTimer = null;
|
||||||
@@ -124,9 +126,30 @@ function applyTabFilter() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const tabBar = container.querySelector(".cl-admin-tabs");
|
// Update filter-active dimming on whichever tab container exists
|
||||||
if (tabBar) {
|
const nativeTabs = container.querySelector(".admin-plugin-config-area__tabs");
|
||||||
tabBar.classList.toggle("filter-active", filterActive);
|
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;
|
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() {
|
function buildTabsUI() {
|
||||||
const container = getContainer();
|
const container = getContainer();
|
||||||
if (!container) return false;
|
if (!container) return false;
|
||||||
|
|
||||||
// Already injected — just re-apply filter
|
// Already injected — just re-apply filter
|
||||||
if (container.querySelector(".cl-admin-tabs")) {
|
if (container.querySelector(".cl-admin-tab")) {
|
||||||
applyTabFilter();
|
applyTabFilter();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -160,7 +198,38 @@ function buildTabsUI() {
|
|||||||
);
|
);
|
||||||
if (!hasOurs) return false;
|
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");
|
const tabBar = document.createElement("div");
|
||||||
tabBar.className = "cl-admin-tabs";
|
tabBar.className = "cl-admin-tabs";
|
||||||
|
|
||||||
@@ -169,31 +238,12 @@ function buildTabsUI() {
|
|||||||
btn.className = "cl-admin-tab" + (tab.id === currentTab ? " active" : "");
|
btn.className = "cl-admin-tab" + (tab.id === currentTab ? " active" : "");
|
||||||
btn.textContent = tab.label;
|
btn.textContent = tab.label;
|
||||||
btn.dataset.tab = tab.id;
|
btn.dataset.tab = tab.id;
|
||||||
btn.addEventListener("click", () => {
|
btn.addEventListener("click", () => handleTabClick(container, tab.id));
|
||||||
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();
|
|
||||||
});
|
|
||||||
tabBar.appendChild(btn);
|
tabBar.appendChild(btn);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Insertion strategy: place tabs as high as possible ──
|
|
||||||
|
|
||||||
let inserted = false;
|
let inserted = false;
|
||||||
|
|
||||||
// Strategy 1: Top of .admin-plugin-config-area__content (above filter bar)
|
|
||||||
const contentArea = container.querySelector(
|
const contentArea = container.querySelector(
|
||||||
".admin-plugin-config-area__content"
|
".admin-plugin-config-area__content"
|
||||||
);
|
);
|
||||||
@@ -204,7 +254,6 @@ function buildTabsUI() {
|
|||||||
inserted = true;
|
inserted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 2: Before the filter controls
|
|
||||||
if (!inserted) {
|
if (!inserted) {
|
||||||
const filterArea = container.querySelector(
|
const filterArea = container.querySelector(
|
||||||
".admin-site-settings-filter-controls, .setting-filter"
|
".admin-site-settings-filter-controls, .setting-filter"
|
||||||
@@ -215,7 +264,6 @@ function buildTabsUI() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 3: Before the first setting row (fallback)
|
|
||||||
if (!inserted) {
|
if (!inserted) {
|
||||||
allRows[0].parentNode.insertBefore(tabBar, allRows[0]);
|
allRows[0].parentNode.insertBefore(tabBar, allRows[0]);
|
||||||
}
|
}
|
||||||
@@ -269,7 +317,7 @@ export default {
|
|||||||
};
|
};
|
||||||
tryInject();
|
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) {
|
if (!recheckTimer) {
|
||||||
recheckTimer = setInterval(() => {
|
recheckTimer = setInterval(() => {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
@@ -278,7 +326,7 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const c = getContainer();
|
const c = getContainer();
|
||||||
if (c && !c.querySelector(".cl-admin-tabs")) {
|
if (c && !c.querySelector(".cl-admin-tab")) {
|
||||||
buildTabsUI();
|
buildTabsUI();
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|||||||
@@ -9,7 +9,64 @@
|
|||||||
display: none !important;
|
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 {
|
.cl-admin-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -20,7 +77,7 @@
|
|||||||
border-bottom: 1px solid var(--primary-low, rgba(0, 0, 0, 0.1));
|
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;
|
padding: 10px 14px;
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
@@ -33,16 +90,15 @@
|
|||||||
transition: color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
|
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);
|
color: var(--primary, #333);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cl-admin-tab.active {
|
.cl-admin-tabs .cl-admin-tab.active {
|
||||||
color: var(--tertiary, #0088cc);
|
color: var(--tertiary, #0088cc);
|
||||||
border-bottom-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 {
|
.cl-admin-tabs.filter-active .cl-admin-tab {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
@@ -51,12 +107,11 @@
|
|||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode tabs */
|
|
||||||
html.dark-scheme .cl-admin-tabs {
|
html.dark-scheme .cl-admin-tabs {
|
||||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
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);
|
color: var(--primary, #ddd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
en:
|
en:
|
||||||
site_settings:
|
site_settings:
|
||||||
# ── Master Switch ──
|
# ── 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 ──
|
# ── 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."
|
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."
|
||||||
|
|||||||
Reference in New Issue
Block a user