feat: internal page links and mentions (#604)

* Work on mentions

* fix: properly parse page slug

* fix editor suggestion bugs

* mentions must start with whitespace

* add icon to page mention render

* feat: backlinks - WIP

* UI - WIP

* permissions check
* use FTS for page suggestion

* cleanup

* WIP

* page title fallback

* feat: handle internal link paste

* link styling

* WIP

* Switch back to LIKE operator for search suggestion

* WIP
* scope to workspaceId
* still create link for pages not found

* select necessary columns

* cleanups
This commit is contained in:
Philip Okugbe
2025-02-14 15:36:44 +00:00
committed by GitHub
parent 0ef6b1978a
commit e209aaa272
46 changed files with 1679 additions and 101 deletions

View File

@ -0,0 +1,72 @@
import {
Backlink,
InsertableBacklink,
UpdatableBacklink,
} from '@docmost/db/types/entity.types';
import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types';
import { dbOrTx } from '@docmost/db/utils';
import { Injectable } from '@nestjs/common';
import { InjectKysely } from 'nestjs-kysely';
@Injectable()
export class BacklinkRepo {
constructor(@InjectKysely() private readonly db: KyselyDB) {}
async findById(
backlinkId: string,
workspaceId: string,
trx?: KyselyTransaction,
): Promise<Backlink> {
const db = dbOrTx(this.db, trx);
return db
.selectFrom('backlinks')
.select([
'id',
'sourcePageId',
'targetPageId',
'workspaceId',
'createdAt',
'updatedAt',
])
.where('id', '=', backlinkId)
.where('workspaceId', '=', workspaceId)
.executeTakeFirst();
}
async insertBacklink(
insertableBacklink: InsertableBacklink,
trx?: KyselyTransaction,
) {
const db = dbOrTx(this.db, trx);
return db
.insertInto('backlinks')
.values(insertableBacklink)
.onConflict((oc) =>
oc.columns(['sourcePageId', 'targetPageId']).doNothing(),
)
.returningAll()
.executeTakeFirst();
}
async updateBacklink(
updatableBacklink: UpdatableBacklink,
backlinkId: string,
trx?: KyselyTransaction,
) {
const db = dbOrTx(this.db, trx);
return db
.updateTable('userTokens')
.set(updatableBacklink)
.where('id', '=', backlinkId)
.execute();
}
async deleteBacklink(
backlinkId: string,
trx?: KyselyTransaction,
): Promise<void> {
const db = dbOrTx(this.db, trx);
await db.deleteFrom('backlinks').where('id', '=', backlinkId).execute();
}
}

View File

@ -166,7 +166,16 @@ export class PageRepo {
.withRecursive('page_hierarchy', (db) =>
db
.selectFrom('pages')
.select(['id', 'slugId', 'title', 'icon', 'content', 'parentPageId', 'spaceId'])
.select([
'id',
'slugId',
'title',
'icon',
'content',
'parentPageId',
'spaceId',
'workspaceId',
])
.where('id', '=', parentPageId)
.unionAll((exp) =>
exp
@ -179,6 +188,7 @@ export class PageRepo {
'p.content',
'p.parentPageId',
'p.spaceId',
'p.workspaceId',
])
.innerJoin('page_hierarchy as ph', 'p.parentPageId', 'ph.id'),
),