track link nodes (backlinks)

This commit is contained in:
Philipinho
2026-04-05 01:35:52 +01:00
parent b38658077f
commit 2277f5d129
4 changed files with 54 additions and 8 deletions
@@ -11,6 +11,7 @@ import {
import {
extractMentions,
extractPageMentions,
extractInternalLinkSlugIds,
} from '../../common/helpers/prosemirror/utils';
import { PageHistoryRepo } from '@docmost/db/repos/page/page-history.repo';
import { PageRepo } from '@docmost/db/repos/page/page.repo';
@@ -77,12 +78,14 @@ export class HistoryProcessor extends WorkerHost implements OnModuleDestroy {
const mentions = extractMentions(page.content);
const pageMentions = extractPageMentions(mentions);
const internalLinkSlugIds = extractInternalLinkSlugIds(page.content);
await this.generalQueue
.add(QueueJob.PAGE_BACKLINKS, {
pageId,
workspaceId: page.workspaceId,
mentions: pageMentions,
internalLinkSlugIds,
} as IPageBacklinkJob)
.catch((err) => {
this.logger.error(
@@ -7,6 +7,10 @@ import { validate as isValidUUID } from 'uuid';
import { Transform } from '@tiptap/pm/transform';
import { TiptapTransformer } from '@hocuspocus/transformer';
import * as Y from 'yjs';
import {
INTERNAL_LINK_REGEX,
extractPageSlugId,
} from '../../../integrations/export/utils';
export interface MentionNode {
id: string;
@@ -64,6 +68,27 @@ export function extractPageMentions(mentionList: MentionNode[]): MentionNode[] {
return pageMentionList as MentionNode[];
}
export function extractInternalLinkSlugIds(prosemirrorJson: any): string[] {
const slugIds: string[] = [];
const doc = jsonToNode(prosemirrorJson);
doc.descendants((node: Node) => {
for (const mark of node.marks) {
if (mark.type.name === 'link' && mark.attrs.internal && mark.attrs.href) {
const match = mark.attrs.href.match(INTERNAL_LINK_REGEX);
if (match) {
const slugId = extractPageSlugId(match[5]);
if (slugId && !slugIds.includes(slugId)) {
slugIds.push(slugId);
}
}
}
}
});
return slugIds;
}
export function extractUserMentionIdsFromJson(json: any): string[] {
const userIds: string[] = [];
@@ -4,6 +4,7 @@ export interface IPageBacklinkJob {
pageId: string;
workspaceId: string;
mentions: MentionNode[];
internalLinkSlugIds?: string[];
}
export interface IAddPageWatchersJob {
@@ -11,7 +11,7 @@ export async function processBacklinks(
backlinkRepo: BacklinkRepo,
data: IPageBacklinkJob,
): Promise<void> {
const { pageId, mentions, workspaceId } = data;
const { pageId, mentions, workspaceId, internalLinkSlugIds = [] } = data;
await executeTx(db, async (trx) => {
const existingBacklinks = await trx
@@ -20,7 +20,28 @@ export async function processBacklinks(
.where('sourcePageId', '=', pageId)
.execute();
if (existingBacklinks.length === 0 && mentions.length === 0) {
const mentionTargetPageIds = mentions
.filter((mention) => mention.entityId !== pageId)
.map((mention) => mention.entityId);
let resolvedLinkPageIds: string[] = [];
if (internalLinkSlugIds.length > 0) {
const resolvedPages = await trx
.selectFrom('pages')
.select('id')
.where('slugId', 'in', internalLinkSlugIds)
.where('workspaceId', '=', workspaceId)
.execute();
resolvedLinkPageIds = resolvedPages
.map((p) => p.id)
.filter((id) => id !== pageId);
}
const allTargetPageIds = [
...new Set([...mentionTargetPageIds, ...resolvedLinkPageIds]),
];
if (existingBacklinks.length === 0 && allTargetPageIds.length === 0) {
return;
}
@@ -28,16 +49,12 @@ export async function processBacklinks(
(backlink) => backlink.targetPageId,
);
const targetPageIds = mentions
.filter((mention) => mention.entityId !== pageId)
.map((mention) => mention.entityId);
let validTargetPages = [];
if (targetPageIds.length > 0) {
if (allTargetPageIds.length > 0) {
validTargetPages = await trx
.selectFrom('pages')
.select('id')
.where('id', 'in', targetPageIds)
.where('id', 'in', allTargetPageIds)
.where('workspaceId', '=', workspaceId)
.execute();
}