-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
docs: add cross-site announcement banner #9326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
6db6b07
191623f
07edbff
2dafaf7
bb83c35
29102e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| .jdx-banner { | ||
| position: relative; | ||
| z-index: 60; | ||
| display: flex; | ||
| gap: 0.75rem; | ||
| align-items: center; | ||
| padding: 0.5rem 1rem; | ||
| background: var(--vp-c-brand-1, #3451b2); | ||
| color: #fff; | ||
| font-size: 0.9rem; | ||
| line-height: 1.4; | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| .jdx-banner a { | ||
| color: #fff; | ||
| text-decoration: underline; | ||
| font-weight: 500; | ||
| } | ||
| .jdx-banner button { | ||
| margin-left: auto; | ||
| background: transparent; | ||
| border: 0; | ||
| color: #fff; | ||
| font-size: 1.25rem; | ||
| cursor: pointer; | ||
| line-height: 1; | ||
| padding: 0 0.25rem; | ||
| opacity: 0.85; | ||
| } | ||
| .jdx-banner button:hover { | ||
| opacity: 1; | ||
| } | ||
| @media (max-width: 640px) { | ||
| .jdx-banner { | ||
| font-size: 0.85rem; | ||
| padding: 0.4rem 0.75rem; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import "./banner.css"; | ||
|
|
||
| interface BannerData { | ||
| id: string; | ||
| enabled: boolean; | ||
| message: string; | ||
| link?: string; | ||
| linkText?: string; | ||
| } | ||
|
|
||
| const ENDPOINT = "https://jdx.dev/banner.json"; | ||
| const STORAGE_KEY = "jdx-banner-dismissed"; | ||
|
|
||
| export function initBanner(): void { | ||
| if (typeof window === "undefined") return; | ||
|
Comment on lines
+14
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The let initialized = false;
export function initBanner(): void {
if (typeof window === "undefined" || initialized) return;
initialized = true; |
||
| fetch(ENDPOINT, { cache: "no-cache" }) | ||
|
greptile-apps[bot] marked this conversation as resolved.
Outdated
greptile-apps[bot] marked this conversation as resolved.
Outdated
|
||
| .then((r) => (r.ok ? (r.json() as Promise<BannerData>) : null)) | ||
| .then((b) => { | ||
| if (!b || !b.enabled) return; | ||
| if (localStorage.getItem(STORAGE_KEY) === b.id) return; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strict equality breaks dismissal for non-string banner IDsMedium Severity The Additional Locations (1)Reviewed by Cursor Bugbot for commit 07edbff. Configure here. |
||
| render(b); | ||
| }) | ||
| .catch(() => {}); | ||
| } | ||
|
|
||
| function isHttpUrl(value: string): boolean { | ||
| try { | ||
| const u = new URL(value, window.location.href); | ||
| return u.protocol === "http:" || u.protocol === "https:"; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| function render(b: BannerData): void { | ||
| const el = document.createElement("div"); | ||
| el.className = "jdx-banner"; | ||
| el.setAttribute("role", "region"); | ||
| el.setAttribute("aria-label", "Site announcement"); | ||
|
|
||
| const msg = document.createElement("span"); | ||
| msg.textContent = b.message; | ||
| el.appendChild(msg); | ||
|
|
||
| if (b.link && isHttpUrl(b.link)) { | ||
| const a = document.createElement("a"); | ||
| a.href = b.link; | ||
| a.target = "_blank"; | ||
| a.rel = "noopener noreferrer"; | ||
| a.textContent = b.linkText || "Learn more"; | ||
| el.appendChild(a); | ||
| } | ||
|
|
||
| const btn = document.createElement("button"); | ||
| btn.type = "button"; | ||
| btn.setAttribute("aria-label", "Dismiss"); | ||
| btn.textContent = "\u00d7"; | ||
| btn.addEventListener("click", () => { | ||
| localStorage.setItem(STORAGE_KEY, b.id); | ||
| el.remove(); | ||
| document.documentElement.style.removeProperty("--vp-layout-top-height"); | ||
| }); | ||
| el.appendChild(btn); | ||
|
|
||
| document.body.prepend(el); | ||
|
|
||
| requestAnimationFrame(() => { | ||
| document.documentElement.style.setProperty( | ||
| "--vp-layout-top-height", | ||
| `${el.offsetHeight}px`, | ||
| ); | ||
| }); | ||
|
Comment on lines
+58
to
+72
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The banner's height can change if the window is resized (e.g., text wrapping on smaller screens). Using a const observer = new ResizeObserver(() => {
document.documentElement.style.setProperty(
"--vp-layout-top-height",
`${el.offsetHeight}px`,
);
});
btn.addEventListener("click", () => {
localStorage.setItem(STORAGE_KEY, b.id);
observer.disconnect();
el.remove();
document.documentElement.style.removeProperty("--vp-layout-top-height");
});
el.appendChild(btn);
document.body.prepend(el);
observer.observe(el); |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import type { Theme } from "vitepress"; | ||
| import DefaultTheme from "vitepress/theme"; | ||
| import { enhanceAppWithTabs } from "vitepress-plugin-tabs/client"; | ||
| import { initBanner } from "./banner"; | ||
| import "virtual:group-icons.css"; | ||
| import "./custom.css"; | ||
| import Layout from "./Layout.vue"; | ||
|
|
@@ -12,6 +13,7 @@ export default { | |
| Layout, | ||
| enhanceApp({ app }) { | ||
| enhanceAppWithTabs(app); | ||
| initBanner(); | ||
|
Comment on lines
14
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }, | ||
| setup() { | ||
| onMounted(() => { | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
position: relativefor the banner while offsetting the fixed navigation bar via--vp-layout-top-heightcauses a visual gap at the top of the viewport when scrolling down. As the banner scrolls away, the navigation bar remains fixed at its offset position. Changing the banner toposition: fixedensures it stays at the top of the viewport along with the navigation bar, providing a consistent user experience.