mirror of
https://github.com/docmost/docmost.git
synced 2026-05-11 00:44:07 +08:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 623182c447 | |||
| 19806eb060 | |||
| 0a2f3e8751 | |||
| 0f234ddc0d | |||
| 2d35662d8e | |||
| 887ef38098 | |||
| c93ea6cfc9 | |||
| 051bc80ab7 | |||
| 78d363febb | |||
| 8681d9a8c4 | |||
| b9543b01bd | |||
| 5510434221 | |||
| f671e7a3b9 | |||
| 974bcea690 | |||
| 601ed88931 | |||
| cfbaedcd63 | |||
| 5fc04aa7df | |||
| c357f169e1 | |||
| 1cbd2854bb | |||
| 3af1482a31 | |||
| d31d1f7bbd | |||
| cc0146d0cd | |||
| 83ce9cf240 | |||
| e7e85e9fdd | |||
| 2d710612b1 | |||
| a0814ef49a | |||
| bf17289ab2 | |||
| c2cd412ac7 | |||
| 71dfcf6bce | |||
| 23e8ab032e | |||
| 0e1d4e5eee | |||
| 6f83f32d5c | |||
| 63ea2f7663 | |||
| 66a3dad632 | |||
| 2adc6a60d2 |
@@ -30,13 +30,18 @@ export class CollaborationHandler {
|
|||||||
updatePageContent: async (
|
updatePageContent: async (
|
||||||
documentName: string,
|
documentName: string,
|
||||||
payload: {
|
payload: {
|
||||||
|
pageId: string;
|
||||||
prosemirrorJson: any;
|
prosemirrorJson: any;
|
||||||
operation: string;
|
operation: string;
|
||||||
user: User;
|
user: User;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const { prosemirrorJson, operation, user } = payload;
|
const { pageId, prosemirrorJson, operation, user } = payload;
|
||||||
this.logger.debug('Updating page content via yjs', documentName);
|
this.logger.debug(
|
||||||
|
'Updating page content via yjs',
|
||||||
|
documentName,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
await this.withYdocConnection(
|
await this.withYdocConnection(
|
||||||
hocuspocus,
|
hocuspocus,
|
||||||
documentName,
|
documentName,
|
||||||
@@ -58,9 +63,7 @@ export class CollaborationHandler {
|
|||||||
} else {
|
} else {
|
||||||
const newContent = prosemirrorJson.content || [];
|
const newContent = prosemirrorJson.content || [];
|
||||||
const yElements = newContent.map(prosemirrorNodeToYElement);
|
const yElements = newContent.map(prosemirrorNodeToYElement);
|
||||||
const position =
|
fragment.insert(fragment.length, yElements);
|
||||||
operation === 'prepend' ? 0 : fragment.length;
|
|
||||||
fragment.insert(position, yElements);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import { Logger, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||||
Global,
|
|
||||||
Logger,
|
|
||||||
Module,
|
|
||||||
OnModuleDestroy,
|
|
||||||
OnModuleInit,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AuthenticationExtension } from './extensions/authentication.extension';
|
import { AuthenticationExtension } from './extensions/authentication.extension';
|
||||||
import { PersistenceExtension } from './extensions/persistence.extension';
|
import { PersistenceExtension } from './extensions/persistence.extension';
|
||||||
import { CollaborationGateway } from './collaboration.gateway';
|
import { CollaborationGateway } from './collaboration.gateway';
|
||||||
|
|||||||
@@ -182,7 +182,6 @@ 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)) {
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
import {
|
|
||||||
initProseMirrorDoc,
|
|
||||||
relativePositionToAbsolutePosition,
|
|
||||||
} from 'y-prosemirror';
|
|
||||||
import * as Y from 'yjs';
|
|
||||||
import { Document } from '@hocuspocus/server';
|
|
||||||
import { getSchema } from '@tiptap/core';
|
|
||||||
import { tiptapExtensions } from './collaboration.util';
|
|
||||||
|
|
||||||
export type YjsSelection = {
|
|
||||||
anchor: any;
|
|
||||||
head: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function setYjsMark(
|
|
||||||
doc: Document,
|
|
||||||
fragment: Y.XmlFragment,
|
|
||||||
yjsSelection: YjsSelection,
|
|
||||||
markName: string,
|
|
||||||
markAttributes: Record<string, any>,
|
|
||||||
) {
|
|
||||||
const schema = getSchema(tiptapExtensions);
|
|
||||||
const { mapping } = initProseMirrorDoc(fragment, schema);
|
|
||||||
|
|
||||||
// Convert JSON positions to Y.js RelativePosition objects
|
|
||||||
const anchorRelPos = Y.createRelativePositionFromJSON(yjsSelection.anchor);
|
|
||||||
const headRelPos = Y.createRelativePositionFromJSON(yjsSelection.head);
|
|
||||||
|
|
||||||
const anchor = relativePositionToAbsolutePosition(
|
|
||||||
doc,
|
|
||||||
fragment,
|
|
||||||
anchorRelPos,
|
|
||||||
mapping,
|
|
||||||
);
|
|
||||||
const head = relativePositionToAbsolutePosition(
|
|
||||||
doc,
|
|
||||||
fragment,
|
|
||||||
headRelPos,
|
|
||||||
mapping,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (anchor === null || head === null) {
|
|
||||||
throw new Error(
|
|
||||||
'Could not resolve Y.js relative positions to absolute positions',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const from = Math.min(anchor, head);
|
|
||||||
const to = Math.max(anchor, head);
|
|
||||||
|
|
||||||
// Apply mark directly to Y.js XmlText nodes
|
|
||||||
// This bypasses updateYFragment which has compatibility issues
|
|
||||||
applyMarkToYFragment(fragment, from, to, markName, markAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyMarkToYFragment(
|
|
||||||
fragment: Y.XmlFragment,
|
|
||||||
from: number,
|
|
||||||
to: number,
|
|
||||||
markName: string,
|
|
||||||
markAttributes: Record<string, any>,
|
|
||||||
) {
|
|
||||||
let pos = 0;
|
|
||||||
|
|
||||||
const processItem = (item: any): boolean => {
|
|
||||||
if (pos >= to) return false;
|
|
||||||
|
|
||||||
if (item instanceof Y.XmlText) {
|
|
||||||
const textLength = item.length;
|
|
||||||
const itemEnd = pos + textLength;
|
|
||||||
|
|
||||||
if (itemEnd > from && pos < to) {
|
|
||||||
const formatFrom = Math.max(0, from - pos);
|
|
||||||
const formatTo = Math.min(textLength, to - pos);
|
|
||||||
const formatLength = formatTo - formatFrom;
|
|
||||||
|
|
||||||
if (formatLength > 0) {
|
|
||||||
item.format(formatFrom, formatLength, { [markName]: markAttributes });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pos = itemEnd;
|
|
||||||
} else if (item instanceof Y.XmlElement) {
|
|
||||||
pos++; // Opening tag
|
|
||||||
for (let i = 0; i < item.length; i++) {
|
|
||||||
if (!processItem(item.get(i))) return false;
|
|
||||||
}
|
|
||||||
pos++; // Closing tag
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < fragment.length; i++) {
|
|
||||||
if (!processItem(fragment.get(i))) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a mark from all text in the fragment that has the specified attribute value.
|
|
||||||
* Useful for deleting comments by commentId.
|
|
||||||
*/
|
|
||||||
export function removeYjsMarkByAttribute(
|
|
||||||
fragment: Y.XmlFragment,
|
|
||||||
markName: string,
|
|
||||||
attributeName: string,
|
|
||||||
attributeValue: string,
|
|
||||||
) {
|
|
||||||
const processItem = (item: any) => {
|
|
||||||
if (item instanceof Y.XmlText) {
|
|
||||||
// Get all formatting deltas to find ranges with this mark
|
|
||||||
const deltas = item.toDelta();
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
for (const delta of deltas) {
|
|
||||||
const length = delta.insert?.length ?? 0;
|
|
||||||
const attributes = delta.attributes ?? {};
|
|
||||||
const markAttr = attributes[markName];
|
|
||||||
|
|
||||||
if (markAttr && markAttr[attributeName] === attributeValue) {
|
|
||||||
// Remove the mark by setting it to null
|
|
||||||
item.format(offset, length, { [markName]: null });
|
|
||||||
}
|
|
||||||
offset += length;
|
|
||||||
}
|
|
||||||
} else if (item instanceof Y.XmlElement) {
|
|
||||||
for (let i = 0; i < item.length; i++) {
|
|
||||||
processItem(item.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < fragment.length; i++) {
|
|
||||||
processItem(fragment.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a mark's attributes for all text that has the specified attribute value.
|
|
||||||
* Useful for resolving/unresolving comments by commentId.
|
|
||||||
*/
|
|
||||||
export function updateYjsMarkAttribute(
|
|
||||||
fragment: Y.XmlFragment,
|
|
||||||
markName: string,
|
|
||||||
findByAttribute: { name: string; value: string },
|
|
||||||
newAttributes: Record<string, any>,
|
|
||||||
) {
|
|
||||||
const processItem = (item: any) => {
|
|
||||||
if (item instanceof Y.XmlText) {
|
|
||||||
const deltas = item.toDelta();
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
for (const delta of deltas) {
|
|
||||||
const length = delta.insert?.length ?? 0;
|
|
||||||
const attributes = delta.attributes ?? {};
|
|
||||||
const markAttr = attributes[markName];
|
|
||||||
|
|
||||||
if (
|
|
||||||
markAttr &&
|
|
||||||
markAttr[findByAttribute.name] === findByAttribute.value
|
|
||||||
) {
|
|
||||||
// Update the mark with new attributes (merge with existing)
|
|
||||||
item.format(offset, length, {
|
|
||||||
[markName]: { ...markAttr, ...newAttributes },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
offset += length;
|
|
||||||
}
|
|
||||||
} else if (item instanceof Y.XmlElement) {
|
|
||||||
for (let i = 0; i < item.length; i++) {
|
|
||||||
processItem(item.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < fragment.length; i++) {
|
|
||||||
processItem(fragment.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,9 +5,8 @@ import {
|
|||||||
IsUUID,
|
IsUUID,
|
||||||
ValidateIf,
|
ValidateIf,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { Transform } from 'class-transformer';
|
|
||||||
|
|
||||||
export type ContentFormat = 'json' | 'markdown' | 'html';
|
export type InputFormat = 'json' | 'markdown' | 'html';
|
||||||
|
|
||||||
export class CreatePageDto {
|
export class CreatePageDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@@ -29,7 +28,6 @@ export class CreatePageDto {
|
|||||||
content?: string | object;
|
content?: string | object;
|
||||||
|
|
||||||
@ValidateIf((o) => o.content !== undefined)
|
@ValidateIf((o) => o.content !== undefined)
|
||||||
@Transform(({ value }) => value?.toLowerCase() ?? 'json')
|
|
||||||
@IsIn(['json', 'markdown', 'html'])
|
@IsIn(['json', 'markdown', 'html'])
|
||||||
format?: ContentFormat;
|
input?: InputFormat;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import {
|
|||||||
IsString,
|
IsString,
|
||||||
IsUUID,
|
IsUUID,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { Transform } from 'class-transformer';
|
|
||||||
|
|
||||||
import { ContentFormat } from './create-page.dto';
|
|
||||||
|
|
||||||
export class PageIdDto {
|
export class PageIdDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -26,19 +23,20 @@ export class PageHistoryIdDto {
|
|||||||
historyId: string;
|
historyId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OutputFormat = 'json' | 'markdown' | 'html';
|
||||||
|
|
||||||
export class PageInfoDto extends PageIdDto {
|
export class PageInfoDto extends PageIdDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
includeSpace: boolean;
|
includeSpace?: boolean;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
includeContent: boolean;
|
includeContent?: boolean;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Transform(({ value }) => value?.toLowerCase())
|
|
||||||
@IsIn(['json', 'markdown', 'html'])
|
@IsIn(['json', 'markdown', 'html'])
|
||||||
format?: ContentFormat;
|
output?: OutputFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DeletePageDto extends PageIdDto {
|
export class DeletePageDto extends PageIdDto {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { PartialType } from '@nestjs/mapped-types';
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
import { CreatePageDto, ContentFormat } from './create-page.dto';
|
import { CreatePageDto, InputFormat } 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';
|
|
||||||
|
|
||||||
export type ContentOperation = 'append' | 'prepend' | 'replace';
|
export type ContentOperation = 'append' | 'replace';
|
||||||
|
|
||||||
export class UpdatePageDto extends PartialType(CreatePageDto) {
|
export class UpdatePageDto extends PartialType(CreatePageDto) {
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -13,12 +12,10 @@ export class UpdatePageDto extends PartialType(CreatePageDto) {
|
|||||||
content?: string | object;
|
content?: string | object;
|
||||||
|
|
||||||
@ValidateIf((o) => o.content !== undefined)
|
@ValidateIf((o) => o.content !== undefined)
|
||||||
@Transform(({ value }) => value?.toLowerCase())
|
@IsIn(['append', 'replace'])
|
||||||
@IsIn(['append', 'prepend', 'replace'])
|
|
||||||
operation?: ContentOperation;
|
operation?: ContentOperation;
|
||||||
|
|
||||||
@ValidateIf((o) => o.content !== undefined)
|
@ValidateIf((o) => o.content !== undefined)
|
||||||
@Transform(({ value }) => value?.toLowerCase() ?? 'json')
|
|
||||||
@IsIn(['json', 'markdown', 'html'])
|
@IsIn(['json', 'markdown', 'html'])
|
||||||
format?: ContentFormat;
|
input?: InputFormat;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ export class PageController {
|
|||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.format && dto.format !== 'json' && page.content) {
|
if (dto.output && dto.output !== 'json' && page.content) {
|
||||||
const contentOutput =
|
const contentOutput =
|
||||||
dto.format === 'markdown'
|
dto.output === 'markdown'
|
||||||
? jsonToMarkdown(page.content)
|
? jsonToMarkdown(page.content)
|
||||||
: jsonToHtml(page.content);
|
: jsonToHtml(page.content);
|
||||||
return {
|
return {
|
||||||
@@ -99,25 +99,7 @@ export class PageController {
|
|||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
const page = await this.pageService.create(
|
return this.pageService.create(user.id, workspace.id, createPageDto);
|
||||||
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)
|
||||||
@@ -134,25 +116,7 @@ export class PageController {
|
|||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedPage = await this.pageService.update(
|
return this.pageService.update(page, updatePageDto, user);
|
||||||
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, ContentFormat } from '../dto/create-page.dto';
|
import { CreatePageDto, InputFormat } 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,11 +99,31 @@ export class PageService {
|
|||||||
let textContent = undefined;
|
let textContent = undefined;
|
||||||
let ydoc = undefined;
|
let ydoc = undefined;
|
||||||
|
|
||||||
if (createPageDto?.content && createPageDto?.format) {
|
if (createPageDto?.content && createPageDto?.input) {
|
||||||
const prosemirrorJson = await this.parseProsemirrorContent(
|
let prosemirrorJson: any;
|
||||||
createPageDto.content,
|
|
||||||
createPageDto.format,
|
switch (createPageDto.input) {
|
||||||
);
|
case 'markdown': {
|
||||||
|
const html = await markdownToHtml(createPageDto.content as string);
|
||||||
|
prosemirrorJson = htmlToJson(html as string);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'html': {
|
||||||
|
prosemirrorJson = htmlToJson(createPageDto.content as string);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'json':
|
||||||
|
default: {
|
||||||
|
prosemirrorJson = createPageDto.content;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
jsonToNode(prosemirrorJson);
|
||||||
|
} catch (err) {
|
||||||
|
throw new BadRequestException('Invalid content format');
|
||||||
|
}
|
||||||
|
|
||||||
content = prosemirrorJson;
|
content = prosemirrorJson;
|
||||||
textContent = jsonToText(prosemirrorJson);
|
textContent = jsonToText(prosemirrorJson);
|
||||||
@@ -193,13 +213,13 @@ export class PageService {
|
|||||||
if (
|
if (
|
||||||
updatePageDto.content &&
|
updatePageDto.content &&
|
||||||
updatePageDto.operation &&
|
updatePageDto.operation &&
|
||||||
updatePageDto.format
|
updatePageDto.input
|
||||||
) {
|
) {
|
||||||
await this.updatePageContent(
|
await this.updatePageContent(
|
||||||
page.id,
|
page.id,
|
||||||
updatePageDto.content,
|
updatePageDto.content,
|
||||||
updatePageDto.operation,
|
updatePageDto.operation,
|
||||||
updatePageDto.format,
|
updatePageDto.input,
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -217,16 +237,39 @@ export class PageService {
|
|||||||
pageId: string,
|
pageId: string,
|
||||||
content: string | object,
|
content: string | object,
|
||||||
operation: ContentOperation,
|
operation: ContentOperation,
|
||||||
format: ContentFormat,
|
input: InputFormat,
|
||||||
user: User,
|
user: User,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const prosemirrorJson = await this.parseProsemirrorContent(content, format);
|
let prosemirrorJson: any;
|
||||||
|
|
||||||
|
switch (input) {
|
||||||
|
case 'markdown': {
|
||||||
|
const html = await markdownToHtml(content as string);
|
||||||
|
prosemirrorJson = htmlToJson(html as string);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'html': {
|
||||||
|
prosemirrorJson = htmlToJson(content as string);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'json':
|
||||||
|
default: {
|
||||||
|
prosemirrorJson = content;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
jsonToNode(prosemirrorJson);
|
||||||
|
} catch (err) {
|
||||||
|
throw new BadRequestException('Invalid content format');
|
||||||
|
}
|
||||||
|
|
||||||
const documentName = `page.${pageId}`;
|
const documentName = `page.${pageId}`;
|
||||||
await this.collaborationGateway.handleYjsEvent(
|
await this.collaborationGateway.handleYjsEvent(
|
||||||
'updatePageContent',
|
'updatePageContent',
|
||||||
documentName,
|
documentName,
|
||||||
{ operation, prosemirrorJson, user },
|
{ pageId, operation, prosemirrorJson, user },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,36 +754,4 @@ export class PageService {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.pageRepo.removePage(pageId, userId, workspaceId);
|
await this.pageRepo.removePage(pageId, userId, workspaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async parseProsemirrorContent(
|
|
||||||
content: string | object,
|
|
||||||
format: ContentFormat,
|
|
||||||
): Promise<any> {
|
|
||||||
let prosemirrorJson: any;
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case 'markdown': {
|
|
||||||
const html = await markdownToHtml(content as string);
|
|
||||||
prosemirrorJson = htmlToJson(html as string);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'html': {
|
|
||||||
prosemirrorJson = htmlToJson(content as string);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'json':
|
|
||||||
default: {
|
|
||||||
prosemirrorJson = content;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
jsonToNode(prosemirrorJson);
|
|
||||||
} catch (err) {
|
|
||||||
throw new BadRequestException('Invalid content format');
|
|
||||||
}
|
|
||||||
|
|
||||||
return prosemirrorJson;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user