Some checks failed
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
- lucide-react (MIT, tree-shaken, compiled into the bundle — no runtime requests; fonts.spec's off-origin assertion covers the page route) - page-actions slot: TopBar registers a DOM element via context, the active page portals its actions into it, TopBar stays page-agnostic - PageActions: mode toggle, watch (WatchToggle icon variant), comments (unread badge kept), attachments, plugin page tools, labels, history as icon buttons with localized aria-label+tooltip (de+en), plus an overflow menu for markdown copy/download, docx/odt/pdf export and the destructive delete (confirm kept) - page header keeps only the title; the editor-shell tools row is gone; panel state lives in PageEditorPage now - hamburger/search/bell adopt the same icon set - e2e: content/export open the overflow menu; class hooks (editor-page__mode-toggle, editor-shell__*-toggle, editor-page__labels-toggle, editor-page__export) kept stable Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
import type { PageCommentsView } from '@dorfteich/shared';
|
|
import { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';
|
|
|
|
import { apiGet } from '../lib/api';
|
|
|
|
/**
|
|
* Page comments data (issue #92). One query per page, shared between the
|
|
* panel and the toggle button's unread badge. "Unread" is a purely local
|
|
* notion: newer than the last time this browser opened the panel
|
|
* (localStorage — no server round-trip, per the issue's design).
|
|
*/
|
|
|
|
export const commentsQueryKey = (pageId: string): [string, string] => ['comments', pageId];
|
|
|
|
export function useComments(pageId: string | undefined): UseQueryResult<PageCommentsView> {
|
|
return useQuery({
|
|
queryKey: commentsQueryKey(pageId ?? ''),
|
|
queryFn: () => apiGet<PageCommentsView>(`/pages/${pageId}/comments`),
|
|
enabled: Boolean(pageId),
|
|
});
|
|
}
|
|
|
|
export function useInvalidateComments(pageId: string): () => Promise<void> {
|
|
const queryClient = useQueryClient();
|
|
return () => queryClient.invalidateQueries({ queryKey: commentsQueryKey(pageId) });
|
|
}
|
|
|
|
const seenKey = (pageId: string): string => `dorfteich.comments.seen.${pageId}`;
|
|
|
|
export function markCommentsSeen(pageId: string): void {
|
|
try {
|
|
localStorage.setItem(seenKey(pageId), new Date().toISOString());
|
|
} catch {
|
|
// Storage full/blocked: the badge simply stays — never break the page.
|
|
}
|
|
}
|
|
|
|
/** Comments newer than the last visit, not authored by the viewer. */
|
|
export function unreadCount(
|
|
view: PageCommentsView | undefined,
|
|
pageId: string,
|
|
viewerId: string | undefined,
|
|
): number {
|
|
if (!view) return 0;
|
|
let since = 0;
|
|
try {
|
|
const stored = localStorage.getItem(seenKey(pageId));
|
|
since = stored ? new Date(stored).getTime() : 0;
|
|
} catch {
|
|
since = 0;
|
|
}
|
|
let count = 0;
|
|
for (const thread of view.threads) {
|
|
for (const comment of [thread.root, ...thread.replies]) {
|
|
if (new Date(comment.createdAt).getTime() > since && comment.author?.id !== viewerId) {
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|