This reverts commit e9680cab74.
This commit is contained in:
parent
e9680cab74
commit
9408e92e44
65 changed files with 361 additions and 1469 deletions
59
live/src/core/helpers/page.ts
Normal file
59
live/src/core/helpers/page.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { getSchema } from "@tiptap/core";
|
||||
import { generateHTML, generateJSON } from "@tiptap/html";
|
||||
import { prosemirrorJSONToYDoc, yXmlFragmentToProseMirrorRootNode } from "y-prosemirror";
|
||||
import * as Y from "yjs"
|
||||
// plane editor
|
||||
import { CoreEditorExtensionsWithoutProps, DocumentEditorExtensionsWithoutProps } from "@plane/editor/lib";
|
||||
|
||||
const DOCUMENT_EDITOR_EXTENSIONS = [
|
||||
...CoreEditorExtensionsWithoutProps,
|
||||
...DocumentEditorExtensionsWithoutProps,
|
||||
];
|
||||
const documentEditorSchema = getSchema(DOCUMENT_EDITOR_EXTENSIONS);
|
||||
|
||||
export const getAllDocumentFormatsFromBinaryData = (description: Uint8Array): {
|
||||
contentBinaryEncoded: string;
|
||||
contentJSON: object;
|
||||
contentHTML: string;
|
||||
} => {
|
||||
// encode binary description data
|
||||
const base64Data = Buffer.from(description).toString("base64");
|
||||
const yDoc = new Y.Doc();
|
||||
Y.applyUpdate(yDoc, description);
|
||||
// convert to JSON
|
||||
const type = yDoc.getXmlFragment("default");
|
||||
const contentJSON = yXmlFragmentToProseMirrorRootNode(
|
||||
type,
|
||||
documentEditorSchema
|
||||
).toJSON();
|
||||
// convert to HTML
|
||||
const contentHTML = generateHTML(contentJSON, DOCUMENT_EDITOR_EXTENSIONS);
|
||||
|
||||
return {
|
||||
contentBinaryEncoded: base64Data,
|
||||
contentJSON,
|
||||
contentHTML,
|
||||
};
|
||||
}
|
||||
|
||||
export const getBinaryDataFromHTMLString = (descriptionHTML: string): {
|
||||
contentBinary: Uint8Array
|
||||
} => {
|
||||
// convert HTML to JSON
|
||||
const contentJSON = generateJSON(
|
||||
descriptionHTML ?? "<p></p>",
|
||||
DOCUMENT_EDITOR_EXTENSIONS
|
||||
);
|
||||
// convert JSON to Y.Doc format
|
||||
const transformedData = prosemirrorJSONToYDoc(
|
||||
documentEditorSchema,
|
||||
contentJSON,
|
||||
"default"
|
||||
);
|
||||
// convert Y.Doc to Uint8Array format
|
||||
const encodedData = Y.encodeStateAsUpdate(transformedData);
|
||||
|
||||
return {
|
||||
contentBinary: encodedData
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// plane editor
|
||||
// helpers
|
||||
import {
|
||||
getAllDocumentFormatsFromDocumentEditorBinaryData,
|
||||
getBinaryDataFromDocumentEditorHTMLString,
|
||||
} from "@plane/editor/lib";
|
||||
getAllDocumentFormatsFromBinaryData,
|
||||
getBinaryDataFromHTMLString,
|
||||
} from "@/core/helpers/page.js";
|
||||
// services
|
||||
import { PageService } from "@/core/services/page.service.js";
|
||||
import { manualLogger } from "../helpers/logger.js";
|
||||
|
|
@ -12,10 +12,12 @@ export const updatePageDescription = async (
|
|||
params: URLSearchParams,
|
||||
pageId: string,
|
||||
updatedDescription: Uint8Array,
|
||||
cookie: string | undefined
|
||||
cookie: string | undefined,
|
||||
) => {
|
||||
if (!(updatedDescription instanceof Uint8Array)) {
|
||||
throw new Error("Invalid updatedDescription: must be an instance of Uint8Array");
|
||||
throw new Error(
|
||||
"Invalid updatedDescription: must be an instance of Uint8Array",
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceSlug = params.get("workspaceSlug")?.toString();
|
||||
|
|
@ -23,7 +25,7 @@ export const updatePageDescription = async (
|
|||
if (!workspaceSlug || !projectId || !cookie) return;
|
||||
|
||||
const { contentBinaryEncoded, contentHTML, contentJSON } =
|
||||
getAllDocumentFormatsFromDocumentEditorBinaryData(updatedDescription);
|
||||
getAllDocumentFormatsFromBinaryData(updatedDescription);
|
||||
try {
|
||||
const payload = {
|
||||
description_binary: contentBinaryEncoded,
|
||||
|
|
@ -31,7 +33,13 @@ export const updatePageDescription = async (
|
|||
description: contentJSON,
|
||||
};
|
||||
|
||||
await pageService.updateDescription(workspaceSlug, projectId, pageId, payload, cookie);
|
||||
await pageService.updateDescription(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
pageId,
|
||||
payload,
|
||||
cookie,
|
||||
);
|
||||
} catch (error) {
|
||||
manualLogger.error("Update error:", error);
|
||||
throw error;
|
||||
|
|
@ -42,16 +50,26 @@ const fetchDescriptionHTMLAndTransform = async (
|
|||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
pageId: string,
|
||||
cookie: string
|
||||
cookie: string,
|
||||
) => {
|
||||
if (!workspaceSlug || !projectId || !cookie) return;
|
||||
|
||||
try {
|
||||
const pageDetails = await pageService.fetchDetails(workspaceSlug, projectId, pageId, cookie);
|
||||
const contentBinary = getBinaryDataFromDocumentEditorHTMLString(pageDetails.description_html ?? "<p></p>");
|
||||
const pageDetails = await pageService.fetchDetails(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
pageId,
|
||||
cookie,
|
||||
);
|
||||
const { contentBinary } = getBinaryDataFromHTMLString(
|
||||
pageDetails.description_html ?? "<p></p>",
|
||||
);
|
||||
return contentBinary;
|
||||
} catch (error) {
|
||||
manualLogger.error("Error while transforming from HTML to Uint8Array", error);
|
||||
manualLogger.error(
|
||||
"Error while transforming from HTML to Uint8Array",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -59,18 +77,28 @@ const fetchDescriptionHTMLAndTransform = async (
|
|||
export const fetchPageDescriptionBinary = async (
|
||||
params: URLSearchParams,
|
||||
pageId: string,
|
||||
cookie: string | undefined
|
||||
cookie: string | undefined,
|
||||
) => {
|
||||
const workspaceSlug = params.get("workspaceSlug")?.toString();
|
||||
const projectId = params.get("projectId")?.toString();
|
||||
if (!workspaceSlug || !projectId || !cookie) return null;
|
||||
|
||||
try {
|
||||
const response = await pageService.fetchDescriptionBinary(workspaceSlug, projectId, pageId, cookie);
|
||||
const response = await pageService.fetchDescriptionBinary(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
pageId,
|
||||
cookie,
|
||||
);
|
||||
const binaryData = new Uint8Array(response);
|
||||
|
||||
if (binaryData.byteLength === 0) {
|
||||
const binary = await fetchDescriptionHTMLAndTransform(workspaceSlug, projectId, pageId, cookie);
|
||||
const binary = await fetchDescriptionHTMLAndTransform(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
pageId,
|
||||
cookie,
|
||||
);
|
||||
if (binary) {
|
||||
return binary;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
// plane editor
|
||||
import {
|
||||
applyUpdates,
|
||||
convertBase64StringToBinaryData,
|
||||
getAllDocumentFormatsFromRichTextEditorBinaryData,
|
||||
} from "@plane/editor/lib";
|
||||
|
||||
export type TResolveConflictsRequestBody = {
|
||||
original_document: string;
|
||||
updates: string;
|
||||
};
|
||||
|
||||
export type TResolveConflictsResponse = {
|
||||
description_binary: string;
|
||||
description_html: string;
|
||||
description: object;
|
||||
};
|
||||
|
||||
export const resolveDocumentConflicts = (body: TResolveConflictsRequestBody): TResolveConflictsResponse => {
|
||||
const { original_document, updates } = body;
|
||||
try {
|
||||
// convert from base64 to buffer
|
||||
const originalDocumentBuffer = original_document ? convertBase64StringToBinaryData(original_document) : null;
|
||||
const updatesBuffer = updates ? convertBase64StringToBinaryData(updates) : null;
|
||||
// decode req.body
|
||||
const decodedOriginalDocument = originalDocumentBuffer ? new Uint8Array(originalDocumentBuffer) : new Uint8Array();
|
||||
const decodedUpdates = updatesBuffer ? new Uint8Array(updatesBuffer) : new Uint8Array();
|
||||
// resolve conflicts
|
||||
let resolvedDocument: Uint8Array;
|
||||
if (decodedOriginalDocument.length === 0) {
|
||||
// use updates to create the document id original_description is null
|
||||
resolvedDocument = applyUpdates(decodedUpdates);
|
||||
} else {
|
||||
// use original document and updates to resolve conflicts
|
||||
resolvedDocument = applyUpdates(decodedOriginalDocument, decodedUpdates);
|
||||
}
|
||||
|
||||
const { contentBinaryEncoded, contentHTML, contentJSON } =
|
||||
getAllDocumentFormatsFromRichTextEditorBinaryData(resolvedDocument);
|
||||
|
||||
return {
|
||||
description_binary: contentBinaryEncoded,
|
||||
description_html: contentHTML,
|
||||
description: contentJSON,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error("Internal server error");
|
||||
}
|
||||
};
|
||||
|
|
@ -5,13 +5,16 @@ import expressWs from "express-ws";
|
|||
import * as Sentry from "@sentry/node";
|
||||
import compression from "compression";
|
||||
import helmet from "helmet";
|
||||
|
||||
// cors
|
||||
import cors from "cors";
|
||||
|
||||
// core hocuspocus server
|
||||
import { getHocusPocusServer } from "@/core/hocuspocus-server.js";
|
||||
|
||||
// helpers
|
||||
import { errorHandler } from "@/core/helpers/error-handler.js";
|
||||
import { logger, manualLogger } from "@/core/helpers/logger.js";
|
||||
import { resolveDocumentConflicts, TResolveConflictsRequestBody } from "@/core/resolve-conflicts.js";
|
||||
import { errorHandler } from "@/core/helpers/error-handler.js";
|
||||
|
||||
const app = express();
|
||||
expressWs(app);
|
||||
|
|
@ -26,7 +29,7 @@ app.use(
|
|||
compression({
|
||||
level: 6,
|
||||
threshold: 5 * 1000,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Logging middleware
|
||||
|
|
@ -59,25 +62,6 @@ router.ws("/collaboration", (ws, req) => {
|
|||
}
|
||||
});
|
||||
|
||||
app.post("/resolve-document-conflicts", (req, res) => {
|
||||
const { original_document, updates } = req.body as TResolveConflictsRequestBody;
|
||||
try {
|
||||
if (original_document === undefined || updates === undefined) {
|
||||
res.status(400).send({
|
||||
message: "Missing required fields",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const resolvedDocument = resolveDocumentConflicts(req.body);
|
||||
res.status(200).json(resolvedDocument);
|
||||
} catch (error) {
|
||||
manualLogger.error("Error in /resolve-document-conflicts endpoint:", error);
|
||||
res.status(500).send({
|
||||
message: "Internal server error",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.use(process.env.LIVE_BASE_PATH || "/live", router);
|
||||
|
||||
app.use((_req, res) => {
|
||||
|
|
@ -98,7 +82,9 @@ const gracefulShutdown = async () => {
|
|||
try {
|
||||
// Close the HocusPocus server WebSocket connections
|
||||
await HocusPocusServer.destroy();
|
||||
manualLogger.info("HocusPocus server WebSocket connections closed gracefully.");
|
||||
manualLogger.info(
|
||||
"HocusPocus server WebSocket connections closed gracefully.",
|
||||
);
|
||||
|
||||
// Close the Express server
|
||||
liveServer.close(() => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue