prefer client timezone for site announcements
This commit is contained in:
@@ -6,6 +6,7 @@ import { clearFocusParams, readFocusAnnouncementId } from './helpers/navigationF
|
|||||||
import {
|
import {
|
||||||
formatSiteAnnouncementSeenAt,
|
formatSiteAnnouncementSeenAt,
|
||||||
readClientTimeZone,
|
readClientTimeZone,
|
||||||
|
resolveSiteAnnouncementTimeZone,
|
||||||
SiteAnnouncementContent,
|
SiteAnnouncementContent,
|
||||||
} from './helpers/siteAnnouncementPresentation.js';
|
} from './helpers/siteAnnouncementPresentation.js';
|
||||||
import { tr } from '../i18n.js';
|
import { tr } from '../i18n.js';
|
||||||
@@ -44,7 +45,7 @@ export default function SiteAnnouncements() {
|
|||||||
const rowRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
const rowRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||||
const highlightTimerRef = useRef<number | null>(null);
|
const highlightTimerRef = useRef<number | null>(null);
|
||||||
const viewerTimeZone = useMemo(() => readClientTimeZone(), []);
|
const viewerTimeZone = useMemo(() => readClientTimeZone(), []);
|
||||||
const displayTimeZone = serverTimeZone || viewerTimeZone;
|
const displayTimeZone = resolveSiteAnnouncementTimeZone(viewerTimeZone, serverTimeZone);
|
||||||
|
|
||||||
const siteNameById = useMemo(() => {
|
const siteNameById = useMemo(() => {
|
||||||
const map = new Map<number, string>();
|
const map = new Map<number, string>();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
SiteAnnouncementContent,
|
SiteAnnouncementContent,
|
||||||
formatSiteAnnouncementSeenAt,
|
formatSiteAnnouncementSeenAt,
|
||||||
readClientTimeZone,
|
readClientTimeZone,
|
||||||
|
resolveSiteAnnouncementTimeZone,
|
||||||
} from './siteAnnouncementPresentation.js';
|
} from './siteAnnouncementPresentation.js';
|
||||||
|
|
||||||
type BrowserGlobals = {
|
type BrowserGlobals = {
|
||||||
@@ -85,6 +86,11 @@ describe('siteAnnouncementPresentation helpers', () => {
|
|||||||
expect(formatSiteAnnouncementSeenAt('2026-03-20 04:23:27', 'Asia/Shanghai')).toBe('2026/03/20 12:23:27');
|
expect(formatSiteAnnouncementSeenAt('2026-03-20 04:23:27', 'Asia/Shanghai')).toBe('2026/03/20 12:23:27');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prefers client timezone over server timezone', () => {
|
||||||
|
expect(resolveSiteAnnouncementTimeZone('Asia/Shanghai', 'UTC')).toBe('Asia/Shanghai');
|
||||||
|
expect(resolveSiteAnnouncementTimeZone('', 'UTC')).toBe('UTC');
|
||||||
|
});
|
||||||
|
|
||||||
it('reads the browser timezone when available', () => {
|
it('reads the browser timezone when available', () => {
|
||||||
expect(readClientTimeZone()).toBeTruthy();
|
expect(readClientTimeZone()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -340,6 +340,17 @@ export function readClientTimeZone(): string | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveSiteAnnouncementTimeZone(
|
||||||
|
clientTimeZone?: string | null,
|
||||||
|
serverTimeZone?: string | null,
|
||||||
|
): string | undefined {
|
||||||
|
const client = String(clientTimeZone || '').trim();
|
||||||
|
if (client) return client;
|
||||||
|
|
||||||
|
const server = String(serverTimeZone || '').trim();
|
||||||
|
return server || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function formatSiteAnnouncementSeenAt(
|
export function formatSiteAnnouncementSeenAt(
|
||||||
value: string | null | undefined,
|
value: string | null | undefined,
|
||||||
timeZone = readClientTimeZone(),
|
timeZone = readClientTimeZone(),
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ vi.mock('../api.js', () => ({
|
|||||||
api: apiMock,
|
api: apiMock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('./helpers/siteAnnouncementPresentation.js', async () => {
|
||||||
|
const actual = await vi.importActual<typeof import('./helpers/siteAnnouncementPresentation.js')>('./helpers/siteAnnouncementPresentation.js');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
readClientTimeZone: () => 'Asia/Shanghai',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
async function flushMicrotasks() {
|
async function flushMicrotasks() {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
@@ -107,7 +115,7 @@ describe('SiteAnnouncements page', () => {
|
|||||||
expect(apiMock.getRuntimeSettings).toHaveBeenCalled();
|
expect(apiMock.getRuntimeSettings).toHaveBeenCalled();
|
||||||
expect(JSON.stringify(root!.toJSON())).toContain('站点公告');
|
expect(JSON.stringify(root!.toJSON())).toContain('站点公告');
|
||||||
expect(JSON.stringify(root!.toJSON())).toContain('Maintenance');
|
expect(JSON.stringify(root!.toJSON())).toContain('Maintenance');
|
||||||
expect(JSON.stringify(root!.toJSON())).toContain('2026/03/20 04:23:27');
|
expect(JSON.stringify(root!.toJSON())).toContain('2026/03/20 12:23:27');
|
||||||
|
|
||||||
const highlightedRows = root!.root.findAll((node) => {
|
const highlightedRows = root!.root.findAll((node) => {
|
||||||
const className = typeof node.props.className === 'string' ? node.props.className : '';
|
const className = typeof node.props.className === 'string' ? node.props.className : '';
|
||||||
|
|||||||
Reference in New Issue
Block a user