[WEB-1116] refactor: page helpers for document transformation (#5503)
* refactor: page helpers for document transformation * refactor: update tranforamtion function name
This commit is contained in:
parent
539acd58f7
commit
9910ed6e5f
9 changed files with 109 additions and 66 deletions
|
|
@ -36,9 +36,9 @@
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/express-ws": "^3.0.4",
|
"@types/express-ws": "^3.0.4",
|
||||||
"@types/node": "^20.14.9",
|
"@types/node": "^20.14.9",
|
||||||
"tsup": "^7.2.0",
|
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
"tsup": "^7.2.0",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,14 +39,15 @@ export const handleAuthentication = async (props: Props) => {
|
||||||
"Authentication failed: Incomplete query params. Either workspaceSlug or projectId is missing."
|
"Authentication failed: Incomplete query params. Either workspaceSlug or projectId is missing."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// fetch current user's roles
|
// fetch current user's project membership info
|
||||||
const workspaceRoles = await userService.getUserAllProjectsRole(
|
const projectMembershipInfo = await userService.getUserProjectMembership(
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
cookie
|
cookie
|
||||||
);
|
);
|
||||||
const currentProjectRole = workspaceRoles[projectId];
|
const projectRole = projectMembershipInfo.role;
|
||||||
// make the connection read only for roles lower than a member
|
// make the connection read only for roles lower than a member
|
||||||
if (currentProjectRole < 15) {
|
if (projectRole < 15) {
|
||||||
connection.readOnly = true;
|
connection.readOnly = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,9 @@
|
||||||
import { getSchema } from "@tiptap/core";
|
// helpers
|
||||||
import { generateHTML, generateJSON } from "@tiptap/html";
|
import { getAllDocumentFormatsFromBinaryData, getBinaryDataFromHTMLString } from "../../core/helpers/page.js";
|
||||||
import * as Y from "yjs";
|
|
||||||
import {
|
|
||||||
prosemirrorJSONToYDoc,
|
|
||||||
yXmlFragmentToProseMirrorRootNode,
|
|
||||||
} from "y-prosemirror";
|
|
||||||
// editor
|
|
||||||
import {
|
|
||||||
CoreEditorExtensionsWithoutProps,
|
|
||||||
DocumentEditorExtensionsWithoutProps,
|
|
||||||
} from "@plane/editor/lib";
|
|
||||||
// services
|
// services
|
||||||
import { PageService } from "../services/page.service.js";
|
import { PageService } from "../services/page.service.js";
|
||||||
const pageService = new PageService();
|
const pageService = new PageService();
|
||||||
|
|
||||||
const DOCUMENT_EDITOR_EXTENSIONS = [
|
|
||||||
...CoreEditorExtensionsWithoutProps,
|
|
||||||
...DocumentEditorExtensionsWithoutProps,
|
|
||||||
];
|
|
||||||
const documentEditorSchema = getSchema(DOCUMENT_EDITOR_EXTENSIONS);
|
|
||||||
|
|
||||||
export const updatePageDescription = async (
|
export const updatePageDescription = async (
|
||||||
params: URLSearchParams,
|
params: URLSearchParams,
|
||||||
pageId: string,
|
pageId: string,
|
||||||
|
|
@ -35,22 +19,15 @@ export const updatePageDescription = async (
|
||||||
const workspaceSlug = params.get("workspaceSlug")?.toString();
|
const workspaceSlug = params.get("workspaceSlug")?.toString();
|
||||||
const projectId = params.get("projectId")?.toString();
|
const projectId = params.get("projectId")?.toString();
|
||||||
if (!workspaceSlug || !projectId || !cookie) return;
|
if (!workspaceSlug || !projectId || !cookie) return;
|
||||||
// encode binary description data
|
|
||||||
const base64Data = Buffer.from(updatedDescription).toString("base64");
|
|
||||||
const yDoc = new Y.Doc();
|
|
||||||
Y.applyUpdate(yDoc, updatedDescription);
|
|
||||||
// convert to JSON
|
|
||||||
const type = yDoc.getXmlFragment("default");
|
|
||||||
const contentJSON = yXmlFragmentToProseMirrorRootNode(
|
|
||||||
type,
|
|
||||||
documentEditorSchema
|
|
||||||
).toJSON();
|
|
||||||
// convert to HTML
|
|
||||||
const contentHTML = generateHTML(contentJSON, DOCUMENT_EDITOR_EXTENSIONS);
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
contentBinaryEncoded,
|
||||||
|
contentHTML,
|
||||||
|
contentJSON
|
||||||
|
} = getAllDocumentFormatsFromBinaryData(updatedDescription);
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
description_binary: base64Data,
|
description_binary: contentBinaryEncoded,
|
||||||
description_html: contentHTML,
|
description_html: contentHTML,
|
||||||
description: contentJSON,
|
description: contentJSON,
|
||||||
};
|
};
|
||||||
|
|
@ -83,23 +60,8 @@ const fetchDescriptionHTMLAndTransform = async (
|
||||||
pageId,
|
pageId,
|
||||||
cookie
|
cookie
|
||||||
);
|
);
|
||||||
// convert already existing html to json
|
const { contentBinary } = getBinaryDataFromHTMLString(pageDetails.description_html ?? "<p></p>")
|
||||||
const contentJSON = generateJSON(
|
return contentBinary;
|
||||||
pageDetails.description_html ?? "<p></p>",
|
|
||||||
DOCUMENT_EDITOR_EXTENSIONS
|
|
||||||
);
|
|
||||||
// get editor schema from the DOCUMENT_EDITOR_EXTENSIONS array
|
|
||||||
const schema = getSchema(DOCUMENT_EDITOR_EXTENSIONS);
|
|
||||||
// convert json to Y.Doc format
|
|
||||||
const transformedData = prosemirrorJSONToYDoc(
|
|
||||||
schema,
|
|
||||||
contentJSON,
|
|
||||||
"default"
|
|
||||||
);
|
|
||||||
// convert Y.Doc to Uint8Array format
|
|
||||||
const encodedData = Y.encodeStateAsUpdate(transformedData);
|
|
||||||
|
|
||||||
return encodedData;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error while transforming from HTML to Uint8Array", error);
|
console.error("Error while transforming from HTML to Uint8Array", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// types
|
// types
|
||||||
import type { IUser, IUserProjectsRole } from "@plane/types";
|
import type { IProjectMember, IUser } from "@plane/types";
|
||||||
// services
|
// services
|
||||||
import { API_BASE_URL, APIService } from "./api.service.js";
|
import { API_BASE_URL, APIService } from "./api.service.js";
|
||||||
|
|
||||||
|
|
@ -26,21 +26,36 @@ export class UserService extends APIService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserAllProjectsRole(
|
async getUserWorkspaceMembership(
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
cookie: string
|
cookie: string
|
||||||
): Promise<IUserProjectsRole> {
|
): Promise<IProjectMember> {
|
||||||
return this.get(
|
return this.get(`/api/workspaces/${workspaceSlug}/workspace-members/me/`,
|
||||||
`/api/users/me/workspaces/${workspaceSlug}/project-roles/`,
|
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Cookie: cookie,
|
Cookie: cookie,
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserProjectMembership(
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
cookie: string
|
||||||
|
): Promise<IProjectMember> {
|
||||||
|
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/project-members/me/`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Cookie: cookie,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
|
import { HocuspocusProvider } from "@hocuspocus/provider";
|
||||||
import { Extensions } from "@tiptap/core";
|
import { Extensions } from "@tiptap/core";
|
||||||
import { SlashCommand } from "@/extensions";
|
import { SlashCommand } from "@/extensions";
|
||||||
// plane editor types
|
// plane editor types
|
||||||
import { TIssueEmbedConfig } from "@/plane-editor/types";
|
import { TIssueEmbedConfig } from "@/plane-editor/types";
|
||||||
// types
|
// types
|
||||||
import { TExtensions, TFileHandler } from "@/types";
|
import { TExtensions, TFileHandler, TUserDetails } from "@/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
disabledExtensions?: TExtensions[];
|
disabledExtensions?: TExtensions[];
|
||||||
fileHandler: TFileHandler;
|
fileHandler: TFileHandler;
|
||||||
issueEmbedConfig: TIssueEmbedConfig | undefined;
|
issueEmbedConfig: TIssueEmbedConfig | undefined;
|
||||||
|
provider: HocuspocusProvider;
|
||||||
|
userDetails: TUserDetails;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DocumentEditorAdditionalExtensions = (props: Props) => {
|
export const DocumentEditorAdditionalExtensions = (props: Props) => {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ import { useEffect, useLayoutEffect, useMemo } from "react";
|
||||||
import { HocuspocusProvider } from "@hocuspocus/provider";
|
import { HocuspocusProvider } from "@hocuspocus/provider";
|
||||||
import Collaboration from "@tiptap/extension-collaboration";
|
import Collaboration from "@tiptap/extension-collaboration";
|
||||||
import { IndexeddbPersistence } from "y-indexeddb";
|
import { IndexeddbPersistence } from "y-indexeddb";
|
||||||
|
// extensions
|
||||||
|
import { SideMenuExtension } from "@/extensions";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEditor } from "@/hooks/use-editor";
|
import { useEditor } from "@/hooks/use-editor";
|
||||||
// plane editor extensions
|
// plane editor extensions
|
||||||
import { DocumentEditorAdditionalExtensions } from "@/plane-editor/extensions";
|
import { DocumentEditorAdditionalExtensions } from "@/plane-editor/extensions";
|
||||||
// types
|
// types
|
||||||
import { TCollaborativeEditorProps } from "@/types";
|
import { TCollaborativeEditorProps } from "@/types";
|
||||||
import { SideMenuExtension } from "@/extensions";
|
|
||||||
|
|
||||||
export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
|
export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -84,6 +85,8 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
|
||||||
disabledExtensions,
|
disabledExtensions,
|
||||||
fileHandler,
|
fileHandler,
|
||||||
issueEmbedConfig: embedHandler?.issue,
|
issueEmbedConfig: embedHandler?.issue,
|
||||||
|
provider,
|
||||||
|
userDetails: user,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
placeholder,
|
placeholder,
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export type TExtensions = "ai" | "issue-embed";
|
export type TExtensions = "ai" | "collaboration-cursor" | "issue-embed";
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,6 @@ export const useEditorFlagging = (): {
|
||||||
documentEditor: TExtensions[];
|
documentEditor: TExtensions[];
|
||||||
richTextEditor: TExtensions[];
|
richTextEditor: TExtensions[];
|
||||||
} => ({
|
} => ({
|
||||||
documentEditor: ["ai"],
|
documentEditor: ["ai", "collaboration-cursor"],
|
||||||
richTextEditor: ["ai"],
|
richTextEditor: ["ai", "collaboration-cursor"],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue