refactor naming

* support prepend
This commit is contained in:
Philipinho
2026-02-12 10:57:30 -08:00
parent 152702ebe0
commit 3c81441ddb
8 changed files with 77 additions and 40 deletions
@@ -30,18 +30,13 @@ export class CollaborationHandler {
updatePageContent: async ( updatePageContent: async (
documentName: string, documentName: string,
payload: { payload: {
pageId: string;
prosemirrorJson: any; prosemirrorJson: any;
operation: string; contentOperation: string;
user: User; user: User;
}, },
) => { ) => {
const { pageId, prosemirrorJson, operation, user } = payload; const { prosemirrorJson, contentOperation, user } = payload;
this.logger.debug( this.logger.debug('Updating page content via yjs', documentName);
'Updating page content via yjs',
documentName,
payload,
);
await this.withYdocConnection( await this.withYdocConnection(
hocuspocus, hocuspocus,
documentName, documentName,
@@ -49,7 +44,7 @@ export class CollaborationHandler {
(doc) => { (doc) => {
const fragment = doc.getXmlFragment('default'); const fragment = doc.getXmlFragment('default');
if (operation === 'replace') { if (contentOperation === 'replace') {
if (fragment.length > 0) { if (fragment.length > 0) {
fragment.delete(0, fragment.length); fragment.delete(0, fragment.length);
} }
@@ -63,7 +58,9 @@ export class CollaborationHandler {
} else { } else {
const newContent = prosemirrorJson.content || []; const newContent = prosemirrorJson.content || [];
const yElements = newContent.map(prosemirrorNodeToYElement); const yElements = newContent.map(prosemirrorNodeToYElement);
fragment.insert(fragment.length, yElements); const position =
contentOperation === 'prepend' ? 0 : fragment.length;
fragment.insert(position, yElements);
} }
}, },
); );
@@ -18,7 +18,6 @@ import { LoggerExtension } from './extensions/logger.extension';
import { CollaborationHandler } from './collaboration.handler'; import { CollaborationHandler } from './collaboration.handler';
import { CollabHistoryService } from './services/collab-history.service'; import { CollabHistoryService } from './services/collab-history.service';
@Global()
@Module({ @Module({
providers: [ providers: [
CollaborationGateway, CollaborationGateway,
@@ -181,7 +181,8 @@ export class PersistenceExtension implements Extension {
async onChange(data: onChangePayload) { async onChange(data: onChangePayload) {
const documentName = data.documentName; const documentName = data.documentName;
const userId = data.context?.user.id; const userId = data.context?.user?.id;
if (!userId) return; if (!userId) return;
if (!this.contributors.has(documentName)) { if (!this.contributors.has(documentName)) {
@@ -7,7 +7,7 @@ import {
} from 'class-validator'; } from 'class-validator';
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
export type InputFormat = 'json' | 'markdown' | 'html'; export type ContentFormat = 'json' | 'markdown' | 'html';
export class CreatePageDto { export class CreatePageDto {
@IsOptional() @IsOptional()
@@ -29,7 +29,7 @@ export class CreatePageDto {
content?: string | object; content?: string | object;
@ValidateIf((o) => o.content !== undefined) @ValidateIf((o) => o.content !== undefined)
@Transform(({ value }) => value?.toLowerCase()) @Transform(({ value }) => value?.toLowerCase() ?? 'json')
@IsIn(['json', 'markdown', 'html']) @IsIn(['json', 'markdown', 'html'])
input?: InputFormat; format?: ContentFormat;
} }
+2 -2
View File
@@ -8,7 +8,7 @@ import {
} from 'class-validator'; } from 'class-validator';
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
export type OutputFormat = 'json' | 'markdown' | 'html'; import { ContentFormat } from './create-page.dto';
export class PageIdDto { export class PageIdDto {
@IsString() @IsString()
@@ -38,7 +38,7 @@ export class PageInfoDto extends PageIdDto {
@IsOptional() @IsOptional()
@Transform(({ value }) => value?.toLowerCase()) @Transform(({ value }) => value?.toLowerCase())
@IsIn(['json', 'markdown', 'html']) @IsIn(['json', 'markdown', 'html'])
output?: OutputFormat; format?: ContentFormat;
} }
export class DeletePageDto extends PageIdDto { export class DeletePageDto extends PageIdDto {
@@ -1,9 +1,9 @@
import { PartialType } from '@nestjs/mapped-types'; import { PartialType } from '@nestjs/mapped-types';
import { CreatePageDto, InputFormat } from './create-page.dto'; import { CreatePageDto, ContentFormat } from './create-page.dto';
import { IsIn, IsOptional, IsString, ValidateIf } from 'class-validator'; import { IsIn, IsOptional, IsString, ValidateIf } from 'class-validator';
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
export type ContentOperation = 'append' | 'replace'; export type ContentOperation = 'append' | 'prepend' | 'replace';
export class UpdatePageDto extends PartialType(CreatePageDto) { export class UpdatePageDto extends PartialType(CreatePageDto) {
@IsString() @IsString()
@@ -14,11 +14,11 @@ export class UpdatePageDto extends PartialType(CreatePageDto) {
@ValidateIf((o) => o.content !== undefined) @ValidateIf((o) => o.content !== undefined)
@Transform(({ value }) => value?.toLowerCase()) @Transform(({ value }) => value?.toLowerCase())
@IsIn(['append', 'replace']) @IsIn(['append', 'prepend', 'replace'])
operation?: ContentOperation; contentOperation?: ContentOperation;
@ValidateIf((o) => o.content !== undefined) @ValidateIf((o) => o.content !== undefined)
@Transform(({ value }) => value?.toLowerCase()) @Transform(({ value }) => value?.toLowerCase() ?? 'json')
@IsIn(['json', 'markdown', 'html']) @IsIn(['json', 'markdown', 'html'])
input?: InputFormat; format?: ContentFormat;
} }
+40 -4
View File
@@ -70,9 +70,9 @@ export class PageController {
throw new ForbiddenException(); throw new ForbiddenException();
} }
if (dto.output && dto.output !== 'json' && page.content) { if (dto.format && dto.format !== 'json' && page.content) {
const contentOutput = const contentOutput =
dto.output === 'markdown' dto.format === 'markdown'
? jsonToMarkdown(page.content) ? jsonToMarkdown(page.content)
: jsonToHtml(page.content); : jsonToHtml(page.content);
return { return {
@@ -99,7 +99,25 @@ export class PageController {
throw new ForbiddenException(); throw new ForbiddenException();
} }
return this.pageService.create(user.id, workspace.id, createPageDto); const page = await this.pageService.create(
user.id,
workspace.id,
createPageDto,
);
if (
createPageDto.format &&
createPageDto.format !== 'json' &&
page.content
) {
const contentOutput =
createPageDto.format === 'markdown'
? jsonToMarkdown(page.content)
: jsonToHtml(page.content);
return { ...page, content: contentOutput };
}
return page;
} }
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@@ -116,7 +134,25 @@ export class PageController {
throw new ForbiddenException(); throw new ForbiddenException();
} }
return this.pageService.update(page, updatePageDto, user); const updatedPage = await this.pageService.update(
page,
updatePageDto,
user,
);
if (
updatePageDto.format &&
updatePageDto.format !== 'json' &&
updatedPage.content
) {
const contentOutput =
updatePageDto.format === 'markdown'
? jsonToMarkdown(updatedPage.content)
: jsonToHtml(updatedPage.content);
return { ...updatedPage, content: contentOutput };
}
return updatedPage;
} }
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@@ -4,7 +4,7 @@ import {
Logger, Logger,
NotFoundException, NotFoundException,
} from '@nestjs/common'; } from '@nestjs/common';
import { CreatePageDto, InputFormat } from '../dto/create-page.dto'; import { CreatePageDto, ContentFormat } from '../dto/create-page.dto';
import { ContentOperation, UpdatePageDto } from '../dto/update-page.dto'; import { ContentOperation, UpdatePageDto } from '../dto/update-page.dto';
import { PageRepo } from '@docmost/db/repos/page/page.repo'; import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { InsertablePage, Page, User } from '@docmost/db/types/entity.types'; import { InsertablePage, Page, User } from '@docmost/db/types/entity.types';
@@ -99,10 +99,10 @@ export class PageService {
let textContent = undefined; let textContent = undefined;
let ydoc = undefined; let ydoc = undefined;
if (createPageDto?.content && createPageDto?.input) { if (createPageDto?.content && createPageDto?.format) {
let prosemirrorJson: any; let prosemirrorJson: any;
switch (createPageDto.input) { switch (createPageDto.format) {
case 'markdown': { case 'markdown': {
const html = await markdownToHtml(createPageDto.content as string); const html = await markdownToHtml(createPageDto.content as string);
prosemirrorJson = htmlToJson(html as string); prosemirrorJson = htmlToJson(html as string);
@@ -212,14 +212,14 @@ export class PageService {
if ( if (
updatePageDto.content && updatePageDto.content &&
updatePageDto.operation && updatePageDto.contentOperation &&
updatePageDto.input updatePageDto.format
) { ) {
await this.updatePageContent( await this.updatePageContent(
page.id, page.id,
updatePageDto.content, updatePageDto.content,
updatePageDto.operation, updatePageDto.contentOperation,
updatePageDto.input, updatePageDto.format,
user, user,
); );
} }
@@ -236,13 +236,13 @@ export class PageService {
async updatePageContent( async updatePageContent(
pageId: string, pageId: string,
content: string | object, content: string | object,
operation: ContentOperation, contentOperation: ContentOperation,
input: InputFormat, format: ContentFormat,
user: User, user: User,
): Promise<void> { ): Promise<void> {
let prosemirrorJson: any; let prosemirrorJson: any;
switch (input) { switch (format) {
case 'markdown': { case 'markdown': {
const html = await markdownToHtml(content as string); const html = await markdownToHtml(content as string);
prosemirrorJson = htmlToJson(html as string); prosemirrorJson = htmlToJson(html as string);
@@ -262,14 +262,14 @@ export class PageService {
try { try {
jsonToNode(prosemirrorJson); jsonToNode(prosemirrorJson);
} catch (err) { } catch (err) {
throw new BadRequestException('Invalid content format'); throw new BadRequestException('Invalid content');
} }
const documentName = `page.${pageId}`; const documentName = `page.${pageId}`;
await this.collaborationGateway.handleYjsEvent( await this.collaborationGateway.handleYjsEvent(
'updatePageContent', 'updatePageContent',
documentName, documentName,
{ pageId, operation, prosemirrorJson, user }, { contentOperation, prosemirrorJson, user },
); );
} }
@@ -306,7 +306,11 @@ export class PageService {
cursor: pagination.cursor, cursor: pagination.cursor,
beforeCursor: pagination.beforeCursor, beforeCursor: pagination.beforeCursor,
fields: [ fields: [
{ expression: 'position', direction: 'asc', orderModifier: (ob) => ob.collate('C').asc() }, {
expression: 'position',
direction: 'asc',
orderModifier: (ob) => ob.collate('C').asc(),
},
{ expression: 'id', direction: 'asc' }, { expression: 'id', direction: 'asc' },
], ],
parseCursor: (cursor) => ({ parseCursor: (cursor) => ({