Release 202506101742
This commit is contained in:
@@ -13,6 +13,7 @@ import HtmxScript from './HtmxScript.astro'
|
||||
import NotificationEventsScript from './NotificationEventsScript.astro'
|
||||
import { makeOgImageUrl } from './OgImage'
|
||||
import ServerEventsScript from './ServerEventsScript.astro'
|
||||
import ServiceWorkerScript from './ServiceWorkerScript.astro'
|
||||
import TailwindJsPluggin from './TailwindJsPluggin.astro'
|
||||
|
||||
import type { ComponentProps } from 'astro/types'
|
||||
@@ -137,10 +138,12 @@ const ogImageUrl = makeOgImageUrl(ogImage, Astro.url)
|
||||
))
|
||||
}
|
||||
|
||||
<!-- Server events -->
|
||||
<ServerEventsScript />
|
||||
|
||||
<!-- Push Notifications -->
|
||||
|
||||
<script src="/src/pwa.ts"></script>
|
||||
<NotificationEventsScript />
|
||||
{
|
||||
Astro.locals.user && (
|
||||
<>
|
||||
<ServerEventsScript />
|
||||
<ServiceWorkerScript />
|
||||
<NotificationEventsScript />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ function addBadgeIfUnread(href: string) {
|
||||
}
|
||||
|
||||
<script>
|
||||
document.addEventListener('sse-new-notification', () => {
|
||||
document.addEventListener('sse:new-notification', () => {
|
||||
const links = document.querySelectorAll('link[rel="icon"]')
|
||||
links.forEach((link) => {
|
||||
const href = link.getAttribute('href')
|
||||
|
||||
@@ -33,6 +33,12 @@ const links = [
|
||||
icon: 'ri:plug-line',
|
||||
external: false,
|
||||
},
|
||||
{
|
||||
href: '/feeds',
|
||||
label: 'RSS',
|
||||
icon: 'ri:rss-line',
|
||||
external: false,
|
||||
},
|
||||
{
|
||||
href: '/about',
|
||||
label: 'About',
|
||||
@@ -49,7 +55,10 @@ const links = [
|
||||
const { class: className, ...htmlProps } = Astro.props
|
||||
---
|
||||
|
||||
<footer class={cn('flex items-center justify-center gap-6 p-4', className)} {...htmlProps}>
|
||||
<footer
|
||||
class={cn('xs:gap-x-6 flex flex-wrap items-center justify-center gap-x-3 gap-y-2 p-4', className)}
|
||||
{...htmlProps}
|
||||
>
|
||||
{
|
||||
links.map(
|
||||
({ href, label, icon, external }) =>
|
||||
@@ -58,9 +67,9 @@ const { class: className, ...htmlProps } = Astro.props
|
||||
href={href}
|
||||
target={external ? '_blank' : undefined}
|
||||
rel={external ? 'noopener noreferrer' : undefined}
|
||||
class="text-day-500 flex items-center gap-1 text-sm transition-colors hover:text-gray-200 hover:underline"
|
||||
class="text-day-500 xs:gap-1 flex items-center gap-0.5 text-sm transition-colors hover:text-gray-200 hover:underline"
|
||||
>
|
||||
<Icon name={icon} class="h-4 w-4" />
|
||||
<Icon name={icon} class="xs:opacity-100 h-4 w-4 opacity-40" />
|
||||
{label}
|
||||
</a>
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ const count =
|
||||
}
|
||||
|
||||
<script>
|
||||
document.addEventListener('sse-new-notification', () => {
|
||||
document.addEventListener('sse:new-notification', () => {
|
||||
document.querySelectorAll<HTMLElement>('[data-notification-count-link]').forEach((link) => {
|
||||
const currentCount = Number(link.getAttribute('data-current-count') || 0)
|
||||
const newCount = currentCount + 1
|
||||
|
||||
61
web/src/components/InputCheckbox.astro
Normal file
61
web/src/components/InputCheckbox.astro
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
import { cn } from '../lib/cn'
|
||||
|
||||
import type InputWrapper from './InputWrapper.astro'
|
||||
import type { AstroChildren } from '../lib/astro'
|
||||
import type { ComponentProps } from 'astro/types'
|
||||
|
||||
type Props = Pick<ComponentProps<typeof InputWrapper>, 'error' | 'name' | 'required'> & {
|
||||
disabled?: boolean
|
||||
id?: string
|
||||
} & (
|
||||
| {
|
||||
label: string
|
||||
children?: undefined
|
||||
}
|
||||
| {
|
||||
label?: undefined
|
||||
children: AstroChildren
|
||||
}
|
||||
)
|
||||
|
||||
const { disabled, name, required, error, id, label } = Astro.props
|
||||
|
||||
const hasError = !!error && error.length > 0
|
||||
---
|
||||
|
||||
{}
|
||||
|
||||
<div>
|
||||
<label
|
||||
class={cn(
|
||||
'inline-flex cursor-pointer items-center gap-2',
|
||||
hasError && 'text-red-300',
|
||||
disabled && 'cursor-not-allowed opacity-50'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
transition:persist
|
||||
type="checkbox"
|
||||
id={id}
|
||||
name={name}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
class={cn(disabled && 'opacity-50')}
|
||||
/>
|
||||
<span class="text-sm leading-none text-pretty">{label ?? <slot />}</span>
|
||||
</label>
|
||||
|
||||
{
|
||||
hasError &&
|
||||
(typeof error === 'string' ? (
|
||||
<p class="text-sm text-red-500">{error}</p>
|
||||
) : (
|
||||
<ul class="text-sm text-red-500">
|
||||
{error.map((e) => (
|
||||
<li>{e}</li>
|
||||
))}
|
||||
</ul>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
@@ -27,6 +27,7 @@ const hasError = !!wrapperProps.error && wrapperProps.error.length > 0
|
||||
class={cn(
|
||||
baseInputClassNames.input,
|
||||
baseInputClassNames.textarea,
|
||||
!!inputProps?.rows && 'h-auto',
|
||||
inputProps?.class,
|
||||
hasError && baseInputClassNames.error,
|
||||
!!inputProps?.disabled && baseInputClassNames.disabled
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { isBrowserNotificationsEnabled, showBrowserNotification } from '../lib/client/browserNotifications'
|
||||
import { makeNotificationOptions } from '../lib/notificationOptions'
|
||||
|
||||
document.addEventListener('sse-new-notification', (event) => {
|
||||
document.addEventListener('sse:new-notification', (event) => {
|
||||
if (isBrowserNotificationsEnabled()) {
|
||||
const payload = event.detail
|
||||
const notification = showBrowserNotification(
|
||||
|
||||
@@ -31,7 +31,7 @@ if (!Astro.locals.user) return
|
||||
const data = JSON.parse(event.data as string)
|
||||
|
||||
if (isServerEventsEvent(data)) {
|
||||
const eventType = `sse-${data.type}` as const
|
||||
const eventType = `sse:${data.type}` as const
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(eventType, { detail: data.data }) as SSEEventMap[typeof eventType]
|
||||
)
|
||||
|
||||
54
web/src/components/ServiceWorkerScript.astro
Normal file
54
web/src/components/ServiceWorkerScript.astro
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<script>
|
||||
import { registerSW } from 'virtual:pwa-register'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
interface Window {
|
||||
__SW_REGISTRATION__?: ServiceWorkerRegistration
|
||||
}
|
||||
}
|
||||
|
||||
const NO_AUTO_RELOAD_ROUTES = ['/account/welcome', '/500', '/404'] as const satisfies `/${string}`[]
|
||||
|
||||
let hasPendingUpdate = false
|
||||
|
||||
const updateSW = registerSW({
|
||||
immediate: true,
|
||||
onRegisteredSW: (_swScriptUrl, registration) => {
|
||||
if (registration) window.__SW_REGISTRATION__ = registration
|
||||
|
||||
document.addEventListener('astro:after-swap', checkAndApplyPendingUpdate, { passive: true })
|
||||
window.addEventListener('popstate', checkAndApplyPendingUpdate, { passive: true })
|
||||
},
|
||||
onNeedRefresh: () => {
|
||||
if (shouldSkipAutoReload()) {
|
||||
void updateSW(false)
|
||||
hasPendingUpdate = true
|
||||
return
|
||||
}
|
||||
|
||||
void updateSW(true)
|
||||
},
|
||||
onRegisterError: (error) => {
|
||||
console.error('Service Worker registration error', error)
|
||||
},
|
||||
})
|
||||
|
||||
function shouldSkipAutoReload() {
|
||||
const currentPath = window.location.pathname
|
||||
const isErrorPage = document.querySelector('[data-is-error-page]') !== null
|
||||
|
||||
return isErrorPage || NO_AUTO_RELOAD_ROUTES.some((route) => currentPath === route)
|
||||
}
|
||||
|
||||
function checkAndApplyPendingUpdate() {
|
||||
if (hasPendingUpdate && !shouldSkipAutoReload()) {
|
||||
hasPendingUpdate = false
|
||||
void updateSW(true)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user