feat(modules): update layout, notifications, questions, rbac, files
- layout: update site-header.tsx - notifications: update use-desktop-notifications.ts - questions: update question-actions.tsx, question-columns.tsx - rbac: update permission-catalog.ts - files: update types.ts
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef, useCallback, useState } from "react"
|
||||
import { useEffect, useRef, useCallback, useSyncExternalStore } from "react"
|
||||
|
||||
import type { Notification } from "../types"
|
||||
|
||||
@@ -22,6 +22,33 @@ interface UseDesktopNotificationsResult {
|
||||
showNotification: (notification: Notification) => void
|
||||
}
|
||||
|
||||
// v3: 使用 useSyncExternalStore 订阅浏览器 Notification 权限。
|
||||
// 模块级监听器集合:requestPermission 完成后手动通知 React 重新读取快照。
|
||||
// Notification API 没有标准的 permissionchange 事件,需手动触发。
|
||||
const permissionListeners = new Set<() => void>()
|
||||
|
||||
function subscribePermission(onStoreChange: () => void): () => void {
|
||||
permissionListeners.add(onStoreChange)
|
||||
return () => {
|
||||
permissionListeners.delete(onStoreChange)
|
||||
}
|
||||
}
|
||||
|
||||
function getPermissionSnapshot(): NotificationPermission | "unsupported" {
|
||||
if (typeof window === "undefined" || !("Notification" in window)) return "unsupported"
|
||||
return Notification.permission
|
||||
}
|
||||
|
||||
function getPermissionServerSnapshot(): NotificationPermission | "unsupported" {
|
||||
// SSR 期间返回固定值,与 client hydration 一致,避免 hydration mismatch。
|
||||
// hydration 完成后 React 自动切换到 getSnapshot 并触发重新渲染。
|
||||
return "unsupported"
|
||||
}
|
||||
|
||||
function notifyPermissionChange(): void {
|
||||
permissionListeners.forEach((listener) => listener())
|
||||
}
|
||||
|
||||
/**
|
||||
* 桌面通知 Hook
|
||||
*
|
||||
@@ -41,15 +68,24 @@ interface UseDesktopNotificationsResult {
|
||||
* - Chrome/Edge/Firefox 桌面版完整支持
|
||||
* - Safari 桌面版 13+ 支持
|
||||
* - 移动端浏览器不支持(自动降级为无桌面推送)
|
||||
*
|
||||
* v3: 使用 useSyncExternalStore 替代 useState + useEffect 检测权限,
|
||||
* 避免 SSR/CSR 不一致导致的 hydration mismatch,同时满足
|
||||
* react-hooks/set-state-in-effect 规则。
|
||||
*/
|
||||
export function useDesktopNotifications(
|
||||
options?: UseDesktopNotificationsOptions
|
||||
): UseDesktopNotificationsResult {
|
||||
const enabled = options?.enabled ?? false
|
||||
const [permission, setPermission] = useState<NotificationPermission | "unsupported">(
|
||||
typeof window !== "undefined" && "Notification" in window
|
||||
? Notification.permission
|
||||
: "unsupported"
|
||||
|
||||
// v3: useSyncExternalStore 自动处理 SSR/CSR 一致性
|
||||
// - SSR: getPermissionServerSnapshot → "unsupported"
|
||||
// - hydration: 仍使用 server snapshot(一致,无 mismatch)
|
||||
// - hydration 后: 切换到 getPermissionSnapshot,如有差异自动 re-render
|
||||
const permission = useSyncExternalStore(
|
||||
subscribePermission,
|
||||
getPermissionSnapshot,
|
||||
getPermissionServerSnapshot
|
||||
)
|
||||
|
||||
const onClickRef = useRef(options?.onClick)
|
||||
@@ -63,8 +99,9 @@ export function useDesktopNotifications(
|
||||
if (typeof window === "undefined" || !("Notification" in window)) return
|
||||
|
||||
try {
|
||||
const result = await Notification.requestPermission()
|
||||
setPermission(result)
|
||||
await Notification.requestPermission()
|
||||
// 通知 React 重新读取快照(Notification.permission 可能已改变)
|
||||
notifyPermissionChange()
|
||||
} catch {
|
||||
// 某些浏览器可能抛出异常,静默处理
|
||||
}
|
||||
@@ -95,17 +132,18 @@ export function useDesktopNotifications(
|
||||
)
|
||||
|
||||
// 当 enabled 变为 true 且权限为 default 时,自动请求权限
|
||||
// 使用异步队列避免在 effect 中同步调用 setState
|
||||
useEffect(() => {
|
||||
if (!enabled || permission !== "default") return
|
||||
if (typeof window === "undefined" || !("Notification" in window)) return
|
||||
|
||||
let cancelled = false
|
||||
Notification.requestPermission().then((result) => {
|
||||
if (!cancelled) setPermission(result)
|
||||
}).catch(() => {
|
||||
// 某些浏览器可能抛出异常,静默处理
|
||||
})
|
||||
Notification.requestPermission()
|
||||
.then(() => {
|
||||
if (!cancelled) notifyPermissionChange()
|
||||
})
|
||||
.catch(() => {
|
||||
// 某些浏览器可能抛出异常,静默处理
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
|
||||
Reference in New Issue
Block a user