Skip to content
37 changes: 37 additions & 0 deletions docs/.vitepress/theme/banner.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.jdx-banner {
position: relative;
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
z-index: 60;
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
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;
}
.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;
}
}
64 changes: 64 additions & 0 deletions docs/.vitepress/theme/banner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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
fetch(ENDPOINT, { cache: 'no-cache' })
.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
render(b)
})
.catch(() => {})
}

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) {
const a = document.createElement('a')
a.href = b.link
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
a.target = '_blank'
a.rel = 'noopener'
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
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')
})
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
el.appendChild(btn)

document.body.prepend(el)

requestAnimationFrame(() => {
document.documentElement.style.setProperty(
'--vp-layout-top-height',
`${el.offsetHeight}px`,
)
})
}
2 changes: 2 additions & 0 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme-without-fonts'
import Layout from './Layout.vue'
import HomePage from './HomePage.vue'
import { initBanner } from './banner'
import './style.css'

export default {
extends: DefaultTheme,
Layout,
enhanceApp({ app, router, siteData }) {
app.component('HomePage', HomePage)
initBanner()
},
} satisfies Theme
Loading