mirror of
https://github.com/docmost/docmost.git
synced 2026-07-26 06:44:49 +10:00
feat: synced blocks (transclusion) (#2163)
* feat: synced blocks (transclusion) * fix:remove name * make placeholders smaller * feat: enforce strict transclusion schema * fix: scope synced blocks to workspace, gate unsync on edit permission * fix collab module error
This commit is contained in:
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
Global,
|
||||
Logger,
|
||||
Module,
|
||||
OnModuleDestroy,
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import { Logger, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
import { AuthenticationExtension } from './extensions/authentication.extension';
|
||||
import { PersistenceExtension } from './extensions/persistence.extension';
|
||||
import { CollaborationGateway } from './collaboration.gateway';
|
||||
@@ -18,6 +12,10 @@ import { LoggerExtension } from './extensions/logger.extension';
|
||||
import { CollaborationHandler } from './collaboration.handler';
|
||||
import { CollabHistoryService } from './services/collab-history.service';
|
||||
import { WatcherModule } from '../core/watcher/watcher.module';
|
||||
import { TransclusionService } from '../core/page/transclusion/transclusion.service';
|
||||
import { TransclusionModule } from '../core/page/transclusion/transclusion.module';
|
||||
import { StorageModule } from '../integrations/storage/storage.module';
|
||||
import { EnvironmentModule } from '../integrations/environment/environment.module';
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
@@ -28,9 +26,17 @@ import { WatcherModule } from '../core/watcher/watcher.module';
|
||||
HistoryProcessor,
|
||||
CollabHistoryService,
|
||||
CollaborationHandler,
|
||||
TransclusionService,
|
||||
],
|
||||
exports: [CollaborationGateway],
|
||||
imports: [TokenModule, WatcherModule],
|
||||
imports: [
|
||||
TokenModule,
|
||||
WatcherModule,
|
||||
StorageModule.forRootAsync({
|
||||
imports: [EnvironmentModule],
|
||||
}),
|
||||
TransclusionModule,
|
||||
],
|
||||
})
|
||||
export class CollaborationModule implements OnModuleInit, OnModuleDestroy {
|
||||
private readonly logger = new Logger(CollaborationModule.name);
|
||||
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
Status,
|
||||
addUniqueIdsToDoc,
|
||||
htmlToMarkdown,
|
||||
TransclusionSource,
|
||||
TransclusionReference,
|
||||
} from '@docmost/editor-ext';
|
||||
import { generateText, getSchema, JSONContent } from '@tiptap/core';
|
||||
import { generateHTML, generateJSON } from '../common/helpers/prosemirror/html';
|
||||
@@ -101,6 +103,8 @@ export const tiptapExtensions = [
|
||||
Columns,
|
||||
Column,
|
||||
Status,
|
||||
TransclusionSource,
|
||||
TransclusionReference,
|
||||
] as any;
|
||||
|
||||
export function jsonToHtml(tiptapJson: any) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
HISTORY_FAST_THRESHOLD,
|
||||
HISTORY_INTERVAL,
|
||||
} from '../constants';
|
||||
import { TransclusionService } from '../../core/page/transclusion/transclusion.service';
|
||||
|
||||
@Injectable()
|
||||
export class PersistenceExtension implements Extension {
|
||||
@@ -45,6 +46,7 @@ export class PersistenceExtension implements Extension {
|
||||
@InjectQueue(QueueName.HISTORY_QUEUE) private historyQueue: Queue,
|
||||
@InjectQueue(QueueName.NOTIFICATION_QUEUE) private notificationQueue: Queue,
|
||||
private readonly collabHistory: CollabHistoryService,
|
||||
private readonly transclusionService: TransclusionService,
|
||||
) {}
|
||||
|
||||
async onLoadDocument(data: onLoadDocumentPayload) {
|
||||
@@ -134,7 +136,11 @@ export class PersistenceExtension implements Extension {
|
||||
try {
|
||||
const existingContributors = page.contributorIds || [];
|
||||
contributorIds = Array.from(
|
||||
new Set([...existingContributors, ...editingUserIds, page.creatorId]),
|
||||
new Set([
|
||||
...existingContributors,
|
||||
...editingUserIds,
|
||||
page.creatorId,
|
||||
]),
|
||||
);
|
||||
} catch (err) {
|
||||
//this.logger.debug('Contributors error:' + err?.['message']);
|
||||
@@ -158,6 +164,10 @@ export class PersistenceExtension implements Extension {
|
||||
this.logger.error(`Failed to update page ${pageId}`, err);
|
||||
}
|
||||
|
||||
if (page) {
|
||||
await this.syncTransclusion(pageId, page.workspaceId, tiptapJson);
|
||||
}
|
||||
|
||||
if (page) {
|
||||
await this.collabHistory.addContributors(pageId, editingUserIds);
|
||||
|
||||
@@ -165,7 +175,9 @@ export class PersistenceExtension implements Extension {
|
||||
|
||||
const userMentions = extractUserMentions(mentions);
|
||||
const oldMentions = page.content ? extractMentions(page.content) : [];
|
||||
const oldMentionedUserIds = extractUserMentions(oldMentions).map((m) => m.entityId);
|
||||
const oldMentionedUserIds = extractUserMentions(oldMentions).map(
|
||||
(m) => m.entityId,
|
||||
);
|
||||
|
||||
if (userMentions.length > 0) {
|
||||
await this.notificationQueue.add(QueueJob.PAGE_MENTION_NOTIFICATION, {
|
||||
@@ -229,4 +241,41 @@ export class PersistenceExtension implements Extension {
|
||||
{ jobId: page.id, delay },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh `page_transclusions` and `page_transclusion_references` to match
|
||||
* the page's current content. Runs outside the page-write transaction and
|
||||
* isolates each call so a failure here cannot affect the page save itself.
|
||||
* The diff is idempotent — the next save converges if a round drops anything.
|
||||
*/
|
||||
private async syncTransclusion(
|
||||
pageId: string,
|
||||
workspaceId: string,
|
||||
tiptapJson: unknown,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.transclusionService.syncPageTransclusions(
|
||||
pageId,
|
||||
workspaceId,
|
||||
tiptapJson,
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
{ err, pageId },
|
||||
'Failed to sync transclusions for page',
|
||||
);
|
||||
}
|
||||
try {
|
||||
await this.transclusionService.syncPageReferences(
|
||||
pageId,
|
||||
workspaceId,
|
||||
tiptapJson,
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
{ err, pageId },
|
||||
'Failed to sync transclusion references for page',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user