From c683bc82a91ef8705cb45c476ddc7229638af13a Mon Sep 17 00:00:00 2001 From: gabeszm Date: Thu, 16 Oct 2025 17:16:53 +0200 Subject: [PATCH] Modernize to .gjs Glimmer component with renderInOutlet pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace plugin outlet connectors with modern .gjs Glimmer component - Use api.renderInOutlet() instead of connector templates (official pattern) - Add service injection (@service router, @service site) - Implement lifecycle hooks with didInsert/willDestroy modifiers - Use native SearchMenu component integration - Remove old connector directory structure - Update CSS to target outlet wrapper classes - Simplify API initializer to 10 lines (from 112 lines) - Add route-based display logic with router service - Direct settings access without this.theme wrapper This follows the official discourse-search-banner implementation pattern. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- common/common.scss | 7 +- .../api-initializers/welcome-banner.js | 112 +----------- .../discourse/components/welcome-banner.gjs | 162 ++++++++++++++++++ .../above-main-outlet/welcome-banner.hbs | 128 -------------- .../above-main-outlet/welcome-banner.js | 7 - .../welcome-banner-header.hbs | 128 -------------- .../welcome-banner-header.js | 7 - 7 files changed, 173 insertions(+), 378 deletions(-) create mode 100644 javascripts/discourse/components/welcome-banner.gjs delete mode 100644 javascripts/discourse/connectors/above-main-outlet/welcome-banner.hbs delete mode 100644 javascripts/discourse/connectors/above-main-outlet/welcome-banner.js delete mode 100644 javascripts/discourse/connectors/below-site-header/welcome-banner-header.hbs delete mode 100644 javascripts/discourse/connectors/below-site-header/welcome-banner-header.js diff --git a/common/common.scss b/common/common.scss index 773248b..23df2e6 100644 --- a/common/common.scss +++ b/common/common.scss @@ -15,6 +15,11 @@ border-radius: 4px; } + .below-site-header-outlet, + .above-main-container-outlet { + display: block; + } + @media (min-width: 800px) { .welcome-card { max-width: 1500px; @@ -22,7 +27,7 @@ margin: 0 auto; } - .below-site-header.welcome-card { + .below-site-header-outlet .welcome-card { max-width: calc(var(--d-sidebar-width, 0px) + var(--d-max-width, 1110px)); } } diff --git a/javascripts/discourse/api-initializers/welcome-banner.js b/javascripts/discourse/api-initializers/welcome-banner.js index 7e2b8db..f853b9f 100644 --- a/javascripts/discourse/api-initializers/welcome-banner.js +++ b/javascripts/discourse/api-initializers/welcome-banner.js @@ -1,112 +1,10 @@ import { apiInitializer } from "discourse/lib/api"; +import WelcomeBanner from "../components/welcome-banner"; export default apiInitializer("1.8.0", (api) => { - api.onPageChange((url) => { - const welcomeCard = document.querySelector(".welcome-card"); - if (!welcomeCard) return; + const outletName = settings.banner_position === "below_header" + ? "below-site-header" + : "above-main-container"; - const showOnPages = settings.show_on_pages || "homepage_only"; - - if (showOnPages === "all_pages") { - // Show on all pages - welcomeCard.style.display = "block"; - } else { - // Show only on homepage - if (url === "/") { - welcomeCard.style.display = "block"; - } else { - welcomeCard.style.display = "none"; - } - } - }); - - const isMobileView = () => document.body.classList.contains("mobile-view"); - - // Render search input with SearchMenu integration - function renderSearchInput() { - // Check if search is enabled in settings (default to true if not set) - const searchEnabled = settings.enable_hero_search !== false; - - if (isMobileView()) return; - - // Try both possible positions for the banner - const container = document.querySelector( - ".welcome-card .search-container" - ); - if (!container) return; - - // Idempotent: exit if already created - if (container.querySelector(".search-input")) return; - - const wrapper = document.createElement("div"); - wrapper.className = "search-wrapper"; - wrapper.setAttribute("role", "search"); - - const input = document.createElement("input"); - input.type = "text"; - input.className = "search-input"; - input.placeholder = settings.search_placeholder || "Keresés a fórumon…"; - input.setAttribute("aria-label", "Search"); - - // Open and sync SearchMenu - const openSearchMenu = () => { - try { - api.openSearchMenu({ - anchorElement: input, - mobileMode: false, - }); - } catch (e) { - // Fallback if method not available - if (console?.debug) { - console.debug("SearchMenu API not available:", e); - } - } - }; - - const syncMenuQuery = () => { - const menuInput = document.querySelector(".search-menu input.search-query"); - if (menuInput && menuInput !== input) { - menuInput.value = input.value; - menuInput.dispatchEvent(new Event("input", { bubbles: true })); - } - }; - - const handleSearchInteraction = () => { - openSearchMenu(); - syncMenuQuery(); - }; - - input.addEventListener("focus", handleSearchInteraction); - input.addEventListener("input", handleSearchInteraction); - - input.addEventListener("keydown", (e) => { - if (e.key === "Enter") { - const q = input.value.trim(); - if (!q) return; - - const form = document.querySelector(".search-menu form"); - const menuInput = document.querySelector(".search-menu input.search-query"); - if (form && menuInput) { - menuInput.value = q; - form.dispatchEvent(new Event("submit", { bubbles: true })); - } else { - window.location.assign(`/search?q=${encodeURIComponent(q)}`); - } - } - }); - - wrapper.appendChild(input); - container.appendChild(wrapper); - } - - const apply = () => { - renderSearchInput(); - }; - - api.onAppEvent("page:changed", () => { - requestAnimationFrame(apply); - }); - - // Initial application - requestAnimationFrame(apply); + api.renderInOutlet(outletName, WelcomeBanner); }); diff --git a/javascripts/discourse/components/welcome-banner.gjs b/javascripts/discourse/components/welcome-banner.gjs new file mode 100644 index 0000000..a158bfb --- /dev/null +++ b/javascripts/discourse/components/welcome-banner.gjs @@ -0,0 +1,162 @@ +import Component from "@glimmer/component"; +import { service } from "@ember/service"; +import { htmlSafe } from "@ember/template"; +import { on } from "@ember/modifier"; +import didInsert from "@ember/render-modifiers/modifiers/did-insert"; +import willDestroy from "@ember/render-modifiers/modifiers/will-destroy"; +import SearchMenu from "discourse/components/search-menu"; + +export default class WelcomeBanner extends Component { + @service router; + @service site; + + get displayForRoute() { + const showOnPages = settings.show_on_pages || "homepage_only"; + const currentRoute = this.router.currentRouteName; + + if (showOnPages === "all_pages") { + return true; + } + + // Show only on homepage + return currentRoute === "discovery.latest" || currentRoute === "discovery.categories"; + } + + get shouldDisplay() { + return settings.enable_welcome_banner && this.displayForRoute; + } + + get isMobile() { + return this.site.mobileView; + } + + didInsert() { + document.documentElement.classList.add("display-welcome-banner"); + } + + willDestroy() { + document.documentElement.classList.remove("display-welcome-banner"); + } + + openSearchMenu(event) { + event.preventDefault(); + // Search menu will be handled by Discourse's native component + } + + +} diff --git a/javascripts/discourse/connectors/above-main-outlet/welcome-banner.hbs b/javascripts/discourse/connectors/above-main-outlet/welcome-banner.hbs deleted file mode 100644 index 9c705cc..0000000 --- a/javascripts/discourse/connectors/above-main-outlet/welcome-banner.hbs +++ /dev/null @@ -1,128 +0,0 @@ -{{#if this.theme.enable_welcome_banner}} -{{#unless (eq this.theme.banner_position "below_header")}} -
-
-
-
-

- {{{this.theme.hero_title_html}}} -

-
- {{{this.theme.hero_content_html}}} -
- {{#if this.theme.enable_hero_search}} -
- {{/if}} -
-
- - -
-{{/unless}} -{{/if}} diff --git a/javascripts/discourse/connectors/above-main-outlet/welcome-banner.js b/javascripts/discourse/connectors/above-main-outlet/welcome-banner.js deleted file mode 100644 index 8cfe340..0000000 --- a/javascripts/discourse/connectors/above-main-outlet/welcome-banner.js +++ /dev/null @@ -1,7 +0,0 @@ -import Component from "@glimmer/component"; - -export default class WelcomeBanner extends Component { - get theme() { - return settings; - } -} diff --git a/javascripts/discourse/connectors/below-site-header/welcome-banner-header.hbs b/javascripts/discourse/connectors/below-site-header/welcome-banner-header.hbs deleted file mode 100644 index aaaa3ae..0000000 --- a/javascripts/discourse/connectors/below-site-header/welcome-banner-header.hbs +++ /dev/null @@ -1,128 +0,0 @@ -{{#if this.theme.enable_welcome_banner}} -{{#if (eq this.theme.banner_position "below_header")}} -
-
-
-
-

- {{{this.theme.hero_title_html}}} -

-
- {{{this.theme.hero_content_html}}} -
- {{#if this.theme.enable_hero_search}} -
- {{/if}} -
-
- - -
-{{/if}} -{{/if}} diff --git a/javascripts/discourse/connectors/below-site-header/welcome-banner-header.js b/javascripts/discourse/connectors/below-site-header/welcome-banner-header.js deleted file mode 100644 index c2b186f..0000000 --- a/javascripts/discourse/connectors/below-site-header/welcome-banner-header.js +++ /dev/null @@ -1,7 +0,0 @@ -import Component from "@glimmer/component"; - -export default class WelcomeBannerHeader extends Component { - get theme() { - return settings; - } -}