mirror of
https://github.com/dpnmw/community-landing.git
synced 2026-03-18 09:27:16 +00:00
346 lines
10 KiB
JavaScript
346 lines
10 KiB
JavaScript
import { withPluginApi } from "discourse/lib/plugin-api";
|
|
|
|
const TABS = [
|
|
{
|
|
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", "staggered_reveal_enabled", "dynamic_background_enabled",
|
|
"mouse_parallax_enabled", "scroll_progress_enabled"
|
|
])
|
|
},
|
|
{
|
|
id: "navbar",
|
|
label: "Navbar",
|
|
settings: new Set([
|
|
"navbar_signin_label", "navbar_join_label", "navbar_bg_color", "navbar_border_style"
|
|
])
|
|
},
|
|
{
|
|
id: "hero",
|
|
label: "Hero",
|
|
settings: new Set([
|
|
"hero_title", "hero_subtitle", "hero_card_enabled", "hero_background_image_url",
|
|
"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"
|
|
])
|
|
},
|
|
{
|
|
id: "stats",
|
|
label: "Stats",
|
|
settings: new Set([
|
|
"stats_title", "stat_icon_color", "stat_icon_bg_color", "stat_icon_shape", "stat_counter_color",
|
|
"stat_members_label", "stat_topics_label", "stat_posts_label",
|
|
"stat_likes_label", "stat_chats_label",
|
|
"stats_bg_dark", "stats_bg_light", "stats_min_height", "stats_border_style"
|
|
])
|
|
},
|
|
{
|
|
id: "about",
|
|
label: "About",
|
|
settings: new Set([
|
|
"about_enabled", "about_heading_enabled", "about_heading",
|
|
"about_title", "about_role", "about_body", "about_image_url",
|
|
"about_gradient_start", "about_gradient_mid", "about_gradient_end",
|
|
"about_background_image_url",
|
|
"about_bg_dark", "about_bg_light", "about_min_height", "about_border_style"
|
|
])
|
|
},
|
|
{
|
|
id: "topics",
|
|
label: "Trending",
|
|
settings: new Set([
|
|
"topics_enabled", "topics_title", "topics_count", "topics_card_bg_color",
|
|
"topics_bg_dark", "topics_bg_light", "topics_min_height", "topics_border_style"
|
|
])
|
|
},
|
|
{
|
|
id: "contributors",
|
|
label: "Creators",
|
|
settings: new Set([
|
|
"contributors_enabled", "contributors_title", "contributors_days", "contributors_count",
|
|
"contributors_bg_dark", "contributors_bg_light", "contributors_min_height", "contributors_border_style"
|
|
])
|
|
},
|
|
{
|
|
id: "groups",
|
|
label: "Spaces",
|
|
settings: new Set([
|
|
"groups_enabled", "groups_title", "groups_count", "groups_selected", "groups_card_bg_color",
|
|
"groups_bg_dark", "groups_bg_light", "groups_min_height", "groups_border_style"
|
|
])
|
|
},
|
|
{
|
|
id: "appcta",
|
|
label: "App CTA",
|
|
settings: new Set([
|
|
"show_app_ctas", "ios_app_url", "android_app_url",
|
|
"ios_app_badge_image_url", "android_app_badge_image_url",
|
|
"app_badge_height", "app_badge_style",
|
|
"app_cta_headline", "app_cta_subtext",
|
|
"app_cta_gradient_start", "app_cta_gradient_mid", "app_cta_gradient_end",
|
|
"app_cta_image_url",
|
|
"app_cta_bg_dark", "app_cta_bg_light", "app_cta_min_height", "app_cta_border_style"
|
|
])
|
|
},
|
|
{
|
|
id: "footer",
|
|
label: "Footer",
|
|
settings: new Set([
|
|
"footer_description", "footer_text", "footer_links",
|
|
"footer_bg_dark", "footer_bg_light", "footer_border_style"
|
|
])
|
|
}
|
|
];
|
|
|
|
let currentTab = "settings";
|
|
let filterActive = false;
|
|
let isActive = false;
|
|
let recheckTimer = null;
|
|
|
|
function getContainer() {
|
|
return (
|
|
document.querySelector(".admin-plugin-config-area") ||
|
|
document.querySelector(".admin-detail")
|
|
);
|
|
}
|
|
|
|
function applyTabFilter() {
|
|
const container = getContainer();
|
|
if (!container) return;
|
|
|
|
const tab = TABS.find((t) => t.id === currentTab);
|
|
if (!tab) return;
|
|
|
|
container.querySelectorAll(".row.setting[data-setting]").forEach((row) => {
|
|
const name = row.getAttribute("data-setting");
|
|
row.classList.toggle(
|
|
"cl-tab-hidden",
|
|
!filterActive && !tab.settings.has(name)
|
|
);
|
|
});
|
|
|
|
// 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");
|
|
}
|
|
}
|
|
|
|
function findFilterInput(container) {
|
|
for (const input of container.querySelectorAll("input")) {
|
|
if (input.closest(".row.setting") || input.closest(".cl-admin-tabs")) {
|
|
continue;
|
|
}
|
|
const t = (input.type || "text").toLowerCase();
|
|
if (t === "text" || t === "search") return input;
|
|
}
|
|
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-tab")) {
|
|
applyTabFilter();
|
|
return true;
|
|
}
|
|
|
|
const allRows = container.querySelectorAll(".row.setting[data-setting]");
|
|
if (allRows.length < 5) return false;
|
|
|
|
// Verify our plugin settings are present
|
|
const hasOurs = Array.from(allRows).some((row) =>
|
|
TABS[0].settings.has(row.getAttribute("data-setting"))
|
|
);
|
|
if (!hasOurs) return false;
|
|
|
|
// ── 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";
|
|
|
|
TABS.forEach((tab) => {
|
|
const btn = document.createElement("button");
|
|
btn.className = "cl-admin-tab" + (tab.id === currentTab ? " active" : "");
|
|
btn.textContent = tab.label;
|
|
btn.dataset.tab = tab.id;
|
|
btn.addEventListener("click", () => handleTabClick(container, tab.id));
|
|
tabBar.appendChild(btn);
|
|
});
|
|
|
|
let inserted = false;
|
|
|
|
const contentArea = container.querySelector(
|
|
".admin-plugin-config-area__content"
|
|
);
|
|
if (contentArea) {
|
|
const form = contentArea.querySelector("form");
|
|
const target = form || contentArea;
|
|
target.insertBefore(tabBar, target.firstChild);
|
|
inserted = true;
|
|
}
|
|
|
|
if (!inserted) {
|
|
const filterArea = container.querySelector(
|
|
".admin-site-settings-filter-controls, .setting-filter"
|
|
);
|
|
if (filterArea) {
|
|
filterArea.parentNode.insertBefore(tabBar, filterArea);
|
|
inserted = true;
|
|
}
|
|
}
|
|
|
|
if (!inserted) {
|
|
allRows[0].parentNode.insertBefore(tabBar, allRows[0]);
|
|
}
|
|
|
|
container.classList.add("cl-tabs-active");
|
|
applyTabFilter();
|
|
return true;
|
|
}
|
|
|
|
// ── Global filter detection via event delegation ──
|
|
// This survives DOM re-renders because it's on document, not on a specific input
|
|
document.addEventListener(
|
|
"input",
|
|
(e) => {
|
|
if (!isActive) return;
|
|
const t = e.target;
|
|
if (!t || !t.closest) return;
|
|
if (t.closest(".row.setting") || t.closest(".cl-admin-tabs")) return;
|
|
|
|
const container = getContainer();
|
|
if (!container || !container.contains(t)) return;
|
|
|
|
const hasText = t.value.trim().length > 0;
|
|
if (hasText !== filterActive) {
|
|
filterActive = hasText;
|
|
applyTabFilter();
|
|
}
|
|
},
|
|
true
|
|
);
|
|
|
|
export default {
|
|
name: "community-landing-admin-tabs",
|
|
|
|
initialize() {
|
|
withPluginApi("1.0", (api) => {
|
|
api.onPageChange((url) => {
|
|
if (
|
|
url.includes("community-landing") ||
|
|
url.includes("community_landing")
|
|
) {
|
|
isActive = true;
|
|
filterActive = false;
|
|
|
|
// Initial injection with retries
|
|
let attempts = 0;
|
|
const tryInject = () => {
|
|
if (buildTabsUI() || attempts > 15) return;
|
|
attempts++;
|
|
setTimeout(tryInject, 200);
|
|
};
|
|
tryInject();
|
|
|
|
// Periodic re-check: re-injects tabs if Discourse re-renders the DOM
|
|
if (!recheckTimer) {
|
|
recheckTimer = setInterval(() => {
|
|
if (!isActive) {
|
|
clearInterval(recheckTimer);
|
|
recheckTimer = null;
|
|
return;
|
|
}
|
|
const c = getContainer();
|
|
if (c && !c.querySelector(".cl-admin-tab")) {
|
|
buildTabsUI();
|
|
}
|
|
}, 500);
|
|
}
|
|
} else {
|
|
// Left plugin settings page — clean up
|
|
isActive = false;
|
|
if (recheckTimer) {
|
|
clearInterval(recheckTimer);
|
|
recheckTimer = null;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
},
|
|
};
|