feat: page history diff (#1891)

* Show actual history changes
* V2 - WIP
* feat: page history diff
* fix: exclude content from history listing

---------

Co-authored-by: Jason Norwood-Young <jason@10layer.com>
This commit is contained in:
Philip Okugbe
2026-02-03 11:55:20 -08:00
committed by GitHub
parent f32bb298e0
commit 5506eb194b
34 changed files with 1434 additions and 154 deletions
@@ -3,6 +3,7 @@ import { OnEvent } from '@nestjs/event-emitter';
import { PageHistoryRepo } from '@docmost/db/repos/page/page-history.repo';
import { Page } from '@docmost/db/types/entity.types';
import { isDeepStrictEqual } from 'node:util';
import { EnvironmentService } from '../../integrations/environment/environment.service';
export class UpdatedPageEvent {
page: Page;
@@ -12,7 +13,10 @@ export class UpdatedPageEvent {
export class HistoryListener {
private readonly logger = new Logger(HistoryListener.name);
constructor(private readonly pageHistoryRepo: PageHistoryRepo) {}
constructor(
private readonly pageHistoryRepo: PageHistoryRepo,
private readonly environmentService: EnvironmentService,
) {}
@OnEvent('collab.page.updated')
async handleCreatePageHistory(event: UpdatedPageEvent) {
@@ -20,13 +24,17 @@ export class HistoryListener {
const pageCreationTime = new Date(page.createdAt).getTime();
const currentTime = Date.now();
const FIVE_MINUTES = 5 * 60 * 1000;
const FIVE_MINUTES = this.environmentService.isDevelopment()
? 60 * 1000
: 5 * 60 * 1000;
if (currentTime - pageCreationTime < FIVE_MINUTES) {
return;
}
const lastHistory = await this.pageHistoryRepo.findPageLastHistory(page.id);
const lastHistory = await this.pageHistoryRepo.findPageLastHistory(page.id, {
includeContent: true,
});
if (
!lastHistory ||
@@ -215,7 +215,6 @@ export class PageController {
}
}
// TODO: scope to workspaces
@HttpCode(HttpStatus.OK)
@Post('/history')
async getPageHistory(
@@ -9,7 +9,9 @@ export class PageHistoryService {
constructor(private pageHistoryRepo: PageHistoryRepo) {}
async findById(historyId: string): Promise<PageHistory> {
return await this.pageHistoryRepo.findById(historyId);
return await this.pageHistoryRepo.findById(historyId, {
includeContent: true,
});
}
async findHistoryByPageId(
@@ -17,15 +17,32 @@ import { DB } from '@docmost/db/types/db';
export class PageHistoryRepo {
constructor(@InjectKysely() private readonly db: KyselyDB) {}
private baseFields: Array<keyof PageHistory> = [
'id',
'pageId',
'slugId',
'title',
'icon',
'coverPhoto',
'lastUpdatedById',
'spaceId',
'workspaceId',
'createdAt',
];
async findById(
pageHistoryId: string,
trx?: KyselyTransaction,
opts?: {
includeContent?: boolean;
trx?: KyselyTransaction;
},
): Promise<PageHistory> {
const db = dbOrTx(this.db, trx);
const db = dbOrTx(this.db, opts?.trx);
return await db
.selectFrom('pageHistory')
.selectAll()
.select(this.baseFields)
.$if(opts?.includeContent, (qb) => qb.select('content'))
.select((eb) => this.withLastUpdatedBy(eb))
.where('id', '=', pageHistoryId)
.executeTakeFirst();
@@ -63,7 +80,7 @@ export class PageHistoryRepo {
async findPageHistoryByPageId(pageId: string, pagination: PaginationOptions) {
const query = this.db
.selectFrom('pageHistory')
.selectAll()
.select(this.baseFields)
.select((eb) => this.withLastUpdatedBy(eb))
.where('pageId', '=', pageId);
@@ -76,12 +93,19 @@ export class PageHistoryRepo {
});
}
async findPageLastHistory(pageId: string, trx?: KyselyTransaction) {
const db = dbOrTx(this.db, trx);
async findPageLastHistory(
pageId: string,
opts?: {
includeContent?: boolean;
trx?: KyselyTransaction;
},
) {
const db = dbOrTx(this.db, opts?.trx);
return await db
.selectFrom('pageHistory')
.selectAll()
.select(this.baseFields)
.$if(opts?.includeContent, (qb) => qb.select('content'))
.where('pageId', '=', pageId)
.limit(1)
.orderBy('createdAt', 'desc')