- Migration: acadenice_backlink table (source/target/link_type/excerpt/workspace) with 3 indexes and UNIQUE(source,target,type) constraint. Up+down. - Backend module AcadeniceBacklinksModule: BacklinkParserService: walks Tiptap JSON, extracts wikilinks/mentions/databaseView. BacklinkIndexerService: idempotent delete-then-insert per page save. BacklinkService: permission-aware query (space_members / public visibility). BacklinksController: GET /api/acadenice/pages/:pageId/backlinks (JWT auth). PageContentUpdatedListener: OnEvent handler for collab saves -> async reindex. Tests: 16 Vitest specs (parser/indexer/service/controller). - PersistenceExtension patch: emits ACADENICE_PAGE_CONTENT_UPDATED_EVENT after each collab onStoreDocument (fire-and-forget, no impact on save path). - CoreModule patch: imports AcadeniceBacklinksModule. - Frontend WikilinkExtension: Tiptap inline atom node, [[Title]] / [[Title|alias]], Suggestion popup (reuses mention pattern + floating-ui), ReactNodeView with broken-link state, insertWikilink command. Tests: 9 Vitest specs (schema/attrs/commands/HTML parse+render). - LinkedReferencesPanel: React Query useBacklinks(pageId, staleTime=30s), accordion grouped by link_type, excerpt preview, navigate to source page. Tests: 7 Vitest specs (loading/error/empty/render/navigate/groups). - extensions.ts patch: + WikilinkExtension in mainExtensions[]. - full-editor.tsx patch: + LinkedReferencesPanel below editor (Divider + panel). - i18n: 11 keys added in en-US and fr-FR (backlinks.* + wikilink.*). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
53 lines
1.3 KiB
TypeScript
53 lines
1.3 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import api from '@/lib/api-client';
|
|
|
|
/**
|
|
* Mirrors the BacklinksResult shape from the backend (R3.2).
|
|
*/
|
|
export interface PageSummary {
|
|
id: string;
|
|
title: string | null;
|
|
slugId: string | null;
|
|
icon: string | null;
|
|
spaceSlug: string | null;
|
|
spaceName: string | null;
|
|
}
|
|
|
|
export interface BacklinkEntry {
|
|
source: PageSummary;
|
|
linkType: 'wikilink' | 'mention' | 'database_embed';
|
|
contextExcerpt: string | null;
|
|
}
|
|
|
|
export interface BacklinksResult {
|
|
wikilinks: BacklinkEntry[];
|
|
mentions: BacklinkEntry[];
|
|
database_embeds: BacklinkEntry[];
|
|
total: number;
|
|
}
|
|
|
|
async function fetchBacklinks(pageId: string): Promise<BacklinksResult> {
|
|
const res = await api.get<BacklinksResult>(
|
|
`/acadenice/pages/${pageId}/backlinks`,
|
|
);
|
|
return res.data;
|
|
}
|
|
|
|
/**
|
|
* React Query hook that fetches backlinks for a given page.
|
|
*
|
|
* Cache policy:
|
|
* - staleTime: 30s — backlinks are eventually consistent (indexed async).
|
|
* - gcTime: 5 min — keep in memory while navigating between pages.
|
|
*
|
|
* The hook is a no-op when pageId is empty/undefined.
|
|
*/
|
|
export function useBacklinks(pageId: string | undefined) {
|
|
return useQuery({
|
|
queryKey: ['acadenice', 'backlinks', pageId],
|
|
queryFn: () => fetchBacklinks(pageId!),
|
|
enabled: !!pageId,
|
|
staleTime: 30_000,
|
|
gcTime: 5 * 60_000,
|
|
});
|
|
}
|