mirror of
https://github.com/makeplane/plane.git
synced 2026-07-14 14:31:37 +02:00
[WIKI-537] [WIKI-539] refactor: document editor (#3758)
* refactor: document editor * chore: update user prop * fix: type warning * refactor: document read only editor * chore: update value prop name * chore: remove unnecessary exports * hore: update initialValue type * chore: revert initialValue type
This commit is contained in:
committed by
GitHub
parent
580729dc20
commit
dd4e733fa9
@@ -2,14 +2,13 @@ import { useRef } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { FileText } from "lucide-react";
|
||||
// editor
|
||||
import { DocumentReadOnlyEditorWithRef, EditorRefApi } from "@plane/editor";
|
||||
// ui
|
||||
// plane imports
|
||||
import { DocumentEditorWithRef, EditorRefApi } from "@plane/editor";
|
||||
import { ERowVariant, Logo, Row } from "@plane/ui";
|
||||
// components
|
||||
import { EditorMentionsRoot } from "@/components/editor";
|
||||
// helpers
|
||||
import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
|
||||
import { getEditorFileHandlers } from "@/helpers/editor.helper";
|
||||
// hooks
|
||||
import { usePublish } from "@/hooks/store";
|
||||
// plane web components
|
||||
@@ -66,16 +65,18 @@ export const PageDetailsMainContent: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="size-full">
|
||||
<DocumentReadOnlyEditorWithRef
|
||||
<DocumentEditorWithRef
|
||||
editable={false}
|
||||
ref={editorRef}
|
||||
id={pageDetails.id}
|
||||
disabledExtensions={[]}
|
||||
flaggedExtensions={[]}
|
||||
initialValue={pageDetails.description_html ?? "<p></p>"}
|
||||
value={pageDetails.description_html ?? "<p></p>"}
|
||||
containerClassName="p-0 pb-64 border-none"
|
||||
fileHandler={getReadOnlyEditorFileHandlers({
|
||||
fileHandler={getEditorFileHandlers({
|
||||
anchor,
|
||||
workspaceId: publishSettings.workspace ?? "",
|
||||
uploadFile: async () => "",
|
||||
})}
|
||||
mentionHandler={{
|
||||
renderComponent: (props) => <EditorMentionsRoot {...props} />,
|
||||
@@ -89,6 +90,7 @@ export const PageDetailsMainContent: React.FC<Props> = observer((props) => {
|
||||
workspaceSlug: "",
|
||||
},
|
||||
}}
|
||||
isSmoothCursorEnabled={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,8 +13,8 @@ import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
|
||||
import { useIssueEmbed } from "@/plane-web/hooks/use-issue-embed";
|
||||
|
||||
type DocumentEditorWrapperProps = MakeOptional<
|
||||
Omit<IDocumentEditorProps, "fileHandler" | "mentionHandler" | "embedHandler" | "isSmoothCursorEnabled" | "user">,
|
||||
"disabledExtensions" | "editable" | "flaggedExtensions"
|
||||
Omit<IDocumentEditorProps, "fileHandler" | "mentionHandler" | "embedHandler" | "user">,
|
||||
"disabledExtensions" | "editable" | "flaggedExtensions" | "isSmoothCursorEnabled"
|
||||
> & {
|
||||
embedHandler?: Partial<IDocumentEditorProps["embedHandler"]>;
|
||||
workspaceSlug: string;
|
||||
|
||||
@@ -43,11 +43,9 @@ export const ProjectFeaturesList: FC<Props> = observer((props) => {
|
||||
[featureProperty]: !currentProjectDetails?.[featureProperty as keyof IProject],
|
||||
};
|
||||
const updateProjectPromise = updateProject(workspaceSlug, projectId, settingsPayload);
|
||||
|
||||
if (featureProperty === "is_time_tracking_enabled") {
|
||||
toggleProjectFeatures(workspaceSlug, projectId, settingsPayload, false);
|
||||
}
|
||||
|
||||
setPromiseToast(updateProjectPromise, {
|
||||
loading: "Updating project feature...",
|
||||
success: {
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane editor
|
||||
import { DocumentReadOnlyEditorWithRef } from "@plane/editor";
|
||||
// plane types
|
||||
// plane imports
|
||||
import { TPage } from "@plane/types";
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
import { calculateTimeAgo, cn, getFileURL, getPageName } from "@plane/utils";
|
||||
// components
|
||||
import { EditorMentionsRoot } from "@/components/editor";
|
||||
// helpers
|
||||
import { DocumentEditor } from "@/components/editor/document/editor";
|
||||
// hooks
|
||||
import { useEditorConfig } from "@/hooks/editor";
|
||||
import { useMember, useWorkspace } from "@/hooks/store";
|
||||
// plane web components
|
||||
// plane web imports
|
||||
import { PageEmbedCardRoot } from "@/plane-web/components/pages";
|
||||
// plane web store
|
||||
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
|
||||
// plane web hooks
|
||||
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
|
||||
import { useIssueEmbed } from "@/plane-web/hooks/use-issue-embed";
|
||||
|
||||
@@ -163,9 +158,10 @@ export const PageEmbedPreview: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
>
|
||||
<div className="h-full overflow-hidden" id={`content-container-${id}`}>
|
||||
<DocumentReadOnlyEditorWithRef
|
||||
<DocumentEditor
|
||||
editable={false}
|
||||
id={id ?? ""}
|
||||
initialValue={description_html ?? "<p></p>"}
|
||||
value={description_html ?? "<p></p>"}
|
||||
containerClassName="p-0 pl-3 border-none"
|
||||
editorClassName="p-2.5 text-xs"
|
||||
disabledExtensions={documentEditorExtensions.disabled}
|
||||
@@ -173,18 +169,7 @@ export const PageEmbedPreview: React.FC<Props> = observer((props) => {
|
||||
displayConfig={{
|
||||
fontSize: "small-font",
|
||||
}}
|
||||
fileHandler={getReadOnlyEditorFileHandlers({
|
||||
projectId: projectId?.toString() ?? "",
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
workspaceId: workspaceDetails?.id?.toString() ?? "",
|
||||
})}
|
||||
mentionHandler={{
|
||||
renderComponent: (props) => <EditorMentionsRoot {...props} />,
|
||||
}}
|
||||
embedHandler={{
|
||||
issue: {
|
||||
widgetCallback: issueEmbedProps.widgetCallback,
|
||||
},
|
||||
page: {
|
||||
widgetCallback: ({ pageId: pageIdFromNode }) => (
|
||||
<PageEmbedCardRoot
|
||||
@@ -197,6 +182,9 @@ export const PageEmbedPreview: React.FC<Props> = observer((props) => {
|
||||
workspaceSlug: workspaceSlug.toString(),
|
||||
},
|
||||
}}
|
||||
projectId={projectId?.toString() ?? ""}
|
||||
workspaceId={workspaceDetails?.id ?? ""}
|
||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,40 +1,39 @@
|
||||
import { useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane editor
|
||||
import { DocumentReadOnlyEditorWithRef, TDisplayConfig } from "@plane/editor";
|
||||
import type { TDisplayConfig } from "@plane/editor";
|
||||
// plane ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { EditorMentionsRoot } from "@/components/editor";
|
||||
import { DocumentEditor } from "@/components/editor/document/editor";
|
||||
import { TVersionEditorProps } from "@/components/pages";
|
||||
// hooks
|
||||
import { useEditorConfig } from "@/hooks/editor";
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
import { usePageFilters } from "@/hooks/use-page-filters";
|
||||
// plane web components
|
||||
import { IssueEmbedCard } from "@/plane-web/components/pages";
|
||||
// plane web hooks
|
||||
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
|
||||
|
||||
export const WorkspacePagesVersionEditor: React.FC<TVersionEditorProps> = observer((props) => {
|
||||
const { activeVersion, versionDetails } = props;
|
||||
// params
|
||||
// navigation
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
// derived values
|
||||
const workspaceDetails = getWorkspaceBySlug(workspaceSlug?.toString() ?? "");
|
||||
// editor config
|
||||
const { getReadOnlyEditorFileHandlers } = useEditorConfig();
|
||||
// editor flagging
|
||||
const { document: documentEditorExtensions } = useEditorFlagging(workspaceSlug?.toString() ?? "");
|
||||
// page filters
|
||||
const { fontSize, fontStyle } = usePageFilters();
|
||||
|
||||
const displayConfig: TDisplayConfig = {
|
||||
fontSize,
|
||||
fontStyle,
|
||||
};
|
||||
const displayConfig: TDisplayConfig = useMemo(
|
||||
() => ({
|
||||
fontSize,
|
||||
fontStyle,
|
||||
}),
|
||||
[fontSize, fontStyle]
|
||||
);
|
||||
|
||||
if (!versionDetails)
|
||||
return (
|
||||
@@ -78,36 +77,21 @@ export const WorkspacePagesVersionEditor: React.FC<TVersionEditorProps> = observ
|
||||
</div>
|
||||
);
|
||||
|
||||
const description = versionDetails?.description_html;
|
||||
if (description === undefined || description?.trim() === "") return null;
|
||||
const description = versionDetails?.description_json;
|
||||
if (!description) return null;
|
||||
|
||||
return (
|
||||
<DocumentReadOnlyEditorWithRef
|
||||
<DocumentEditor
|
||||
editable={false}
|
||||
id={activeVersion ?? ""}
|
||||
initialValue={description ?? "<p></p>"}
|
||||
value={description}
|
||||
containerClassName="p-0 pb-64 border-none"
|
||||
disabledExtensions={documentEditorExtensions.disabled}
|
||||
flaggedExtensions={documentEditorExtensions.flagged}
|
||||
displayConfig={displayConfig}
|
||||
editorClassName="pl-10"
|
||||
fileHandler={getReadOnlyEditorFileHandlers({
|
||||
workspaceId: workspaceDetails?.id ?? "",
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
})}
|
||||
mentionHandler={{
|
||||
renderComponent: (props) => <EditorMentionsRoot {...props} />,
|
||||
}}
|
||||
embedHandler={{
|
||||
issue: {
|
||||
widgetCallback: ({ issueId, projectId: projectIdFromEmbed, workspaceSlug: workspaceSlugFromEmbed }) => {
|
||||
const resolvedProjectId = projectIdFromEmbed ?? "";
|
||||
const resolvedWorkspaceSlug = workspaceSlugFromEmbed ?? workspaceSlug?.toString() ?? "";
|
||||
return (
|
||||
<IssueEmbedCard issueId={issueId} projectId={resolvedProjectId} workspaceSlug={resolvedWorkspaceSlug} />
|
||||
);
|
||||
},
|
||||
},
|
||||
}}
|
||||
workspaceId={workspaceDetails?.id ?? ""}
|
||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { DocumentEditorWithRef, TExtensions } from "@plane/editor";
|
||||
import { EFileAssetType, TSearchEntityRequestPayload } from "@plane/types";
|
||||
import { generateRandomColor, hslToHex } from "@plane/utils";
|
||||
// components
|
||||
import { EditorMentionsRoot } from "@/components/editor";
|
||||
import { DocumentEditor } from "@/components/editor/document/editor";
|
||||
// hooks
|
||||
import { useEditorConfig, useEditorMention } from "@/hooks/editor";
|
||||
import { useEditorAsset, useMember, useUser, useUserProfile, useWorkspace } from "@/hooks/store";
|
||||
// plane web hooks
|
||||
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
|
||||
import { useIssueEmbed } from "@/plane-web/hooks/use-issue-embed";
|
||||
import { useEditorAsset, useWorkspace } from "@/hooks/store";
|
||||
// services
|
||||
import { WorkspaceService } from "@/plane-web/services";
|
||||
const workspaceService = new WorkspaceService();
|
||||
@@ -26,97 +20,44 @@ type Props = {
|
||||
|
||||
export const PageTemplateEditor = observer((props: Props) => {
|
||||
const { workspaceSlug, projectId, templateId, initialValue, onChange } = props;
|
||||
// store hooks
|
||||
const { data: currentUser } = useUser();
|
||||
const {
|
||||
data: { is_smooth_cursor_enabled },
|
||||
} = useUserProfile();
|
||||
const { getUserDetails } = useMember();
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const { uploadEditorAsset } = useEditorAsset();
|
||||
const { getEditorFileHandlers } = useEditorConfig();
|
||||
const { document: documentEditorExtensions } = useEditorFlagging(workspaceSlug);
|
||||
// derived values
|
||||
const additionalDisabledExtensions: TExtensions[] = ["issue-embed"];
|
||||
const disabledExtensions = [...documentEditorExtensions.disabled, ...additionalDisabledExtensions];
|
||||
const workspaceId = useMemo(
|
||||
() => (workspaceSlug ? (getWorkspaceBySlug(workspaceSlug)?.id ?? "") : ""),
|
||||
[getWorkspaceBySlug, workspaceSlug]
|
||||
);
|
||||
// user config
|
||||
const userConfig = useMemo(
|
||||
() => ({
|
||||
id: currentUser?.id ?? "",
|
||||
name: currentUser?.display_name ?? "",
|
||||
color: hslToHex(generateRandomColor(currentUser?.id ?? "")),
|
||||
}),
|
||||
[currentUser?.display_name, currentUser?.id]
|
||||
);
|
||||
// editor configs
|
||||
// file handler
|
||||
const fileHandler = useMemo(
|
||||
() =>
|
||||
getEditorFileHandlers({
|
||||
uploadFile: async (blockId, file) => {
|
||||
const { asset_id } = await uploadEditorAsset({
|
||||
blockId,
|
||||
file,
|
||||
data: {
|
||||
entity_identifier: templateId ?? "",
|
||||
entity_type: EFileAssetType.PAGE_TEMPLATE_DESCRIPTION,
|
||||
},
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
});
|
||||
return asset_id;
|
||||
},
|
||||
workspaceId,
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
}),
|
||||
[getEditorFileHandlers, templateId, uploadEditorAsset, workspaceId, workspaceSlug]
|
||||
);
|
||||
// entity search handler
|
||||
const fetchEntityCallback = useCallback(
|
||||
async (payload: TSearchEntityRequestPayload) =>
|
||||
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", {
|
||||
...payload,
|
||||
project_id: projectId?.toString() ?? "",
|
||||
}),
|
||||
[projectId, workspaceSlug]
|
||||
);
|
||||
// issue-embed
|
||||
const { issueEmbedProps } = useIssueEmbed({
|
||||
fetchEmbedSuggestions: fetchEntityCallback,
|
||||
workspaceSlug,
|
||||
});
|
||||
// use editor mention
|
||||
const { fetchMentions } = useEditorMention({
|
||||
searchEntity: fetchEntityCallback,
|
||||
});
|
||||
|
||||
return (
|
||||
<DocumentEditorWithRef
|
||||
disabledExtensions={disabledExtensions}
|
||||
flaggedExtensions={documentEditorExtensions.flagged}
|
||||
fileHandler={fileHandler}
|
||||
<DocumentEditor
|
||||
disabledExtensions={["issue-embed"]}
|
||||
editable
|
||||
id="page-template-editor"
|
||||
initialValue={initialValue}
|
||||
value={initialValue}
|
||||
onChange={(json, html) => onChange(json, html)}
|
||||
user={userConfig}
|
||||
embedHandler={{
|
||||
issue: issueEmbedProps,
|
||||
}}
|
||||
mentionHandler={{
|
||||
searchCallback: async (query) => {
|
||||
const res = await fetchMentions(query);
|
||||
if (!res) throw new Error("Failed in fetching mentions");
|
||||
return res;
|
||||
},
|
||||
renderComponent: (props) => <EditorMentionsRoot {...props} />,
|
||||
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
|
||||
}}
|
||||
containerClassName="min-h-[120px] border-none pl-4 -ml-4"
|
||||
isSmoothCursorEnabled={is_smooth_cursor_enabled}
|
||||
uploadFile={async (blockId, file) => {
|
||||
const { asset_id } = await uploadEditorAsset({
|
||||
blockId,
|
||||
file,
|
||||
data: {
|
||||
entity_identifier: templateId ?? "",
|
||||
entity_type: EFileAssetType.PAGE_TEMPLATE_DESCRIPTION,
|
||||
},
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
});
|
||||
return asset_id;
|
||||
}}
|
||||
searchMentionCallback={async (payload: TSearchEntityRequestPayload) =>
|
||||
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", {
|
||||
...payload,
|
||||
project_id: projectId?.toString() ?? "",
|
||||
})
|
||||
}
|
||||
bubbleMenuEnabled
|
||||
projectId={projectId}
|
||||
workspaceId={workspaceId}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -30,8 +30,8 @@ const DocumentEditor = (props: IDocumentEditorProps) => {
|
||||
flaggedExtensions,
|
||||
forwardedRef,
|
||||
id,
|
||||
isSmoothCursorEnabled,
|
||||
handleEditorReady,
|
||||
isSmoothCursorEnabled,
|
||||
mentionHandler,
|
||||
onChange,
|
||||
user,
|
||||
|
||||
@@ -188,10 +188,12 @@ export interface ICollaborativeDocumentEditorProps
|
||||
titleRef?: React.MutableRefObject<EditorTitleRefApi | null>;
|
||||
}
|
||||
|
||||
export interface IDocumentEditor extends Omit<IEditorProps, "onEnterKeyPress" | "value"> {
|
||||
export interface IDocumentEditorProps extends Omit<IEditorProps, "initialValue" | "onEnterKeyPress" | "value"> {
|
||||
aiHandler?: TAIHandler;
|
||||
editable: boolean;
|
||||
embedHandler: TEmbedConfig;
|
||||
user: TUserDetails;
|
||||
user?: TUserDetails;
|
||||
value: Content;
|
||||
}
|
||||
|
||||
export interface IDocumentEditorProps extends Omit<IEditorProps, "initialValue" | "onEnterKeyPress" | "value"> {
|
||||
|
||||
Reference in New Issue
Block a user