Compare commits

...

1 Commits

Author SHA1 Message Date
Aaryan Khandelwal
2cf7059463 refactor: custom image extension 2024-10-18 18:42:15 +05:30
26 changed files with 222 additions and 287 deletions

View File

@@ -10,7 +10,7 @@ import { getEditorClassNames } from "@/helpers/common";
// hooks
import { useReadOnlyEditor } from "@/hooks/use-read-only-editor";
// types
import { EditorReadOnlyRefApi, IMentionHighlight, TDisplayConfig, TFileHandler } from "@/types";
import { EditorReadOnlyRefApi, IMentionHighlight, TDisplayConfig, TReadOnlyFileHandler } from "@/types";
interface IDocumentReadOnlyEditor {
id: string;
@@ -19,7 +19,7 @@ interface IDocumentReadOnlyEditor {
displayConfig?: TDisplayConfig;
editorClassName?: string;
embedHandler: any;
fileHandler: Pick<TFileHandler, "getAssetSrc">;
fileHandler: TReadOnlyFileHandler;
tabIndex?: number;
handleEditorReady?: (value: boolean) => void;
mentionHandler: {

View File

@@ -11,12 +11,12 @@ import { CustomCodeInlineExtension } from "./code-inline";
import { CustomLinkExtension } from "./custom-link";
import { CustomHorizontalRule } from "./horizontal-rule";
import { ImageExtensionWithoutProps } from "./image";
import { CustomImageComponentWithoutProps } from "./image/image-component-without-props";
import { IssueWidgetWithoutProps } from "./issue-embed/issue-embed-without-props";
import { CustomMentionWithoutProps } from "./mentions/mentions-without-props";
import { CustomQuoteExtension } from "./quote";
import { TableHeader, TableCell, TableRow, Table } from "./table";
import { CustomColorExtension } from "./custom-color";
import { CustomImageExtensionConfig } from "./custom-image/extension-config";
export const CoreEditorExtensionsWithoutProps = [
StarterKit.configure({
@@ -63,7 +63,7 @@ export const CoreEditorExtensionsWithoutProps = [
class: "rounded-md",
},
}),
CustomImageComponentWithoutProps(),
CustomImageExtensionConfig(),
TiptapUnderline,
TextStyle,
TaskList.configure({

View File

@@ -1,13 +1,12 @@
import { useEffect, useRef, useState } from "react";
import { Node as ProsemirrorNode } from "@tiptap/pm/model";
import { Editor, NodeViewWrapper } from "@tiptap/react";
import { Editor, NodeViewProps, NodeViewWrapper } from "@tiptap/react";
// extensions
import { CustomImageBlock, CustomImageUploader, ImageAttributes } from "@/extensions/custom-image";
export type CustomImageNodeViewProps = {
export type CustomImageNodeViewProps = NodeViewProps & {
getPos: () => number;
editor: Editor;
node: ProsemirrorNode & {
node: NodeViewProps["node"] & {
attrs: ImageAttributes;
};
updateAttributes: (attrs: Record<string, any>) => void;

View File

@@ -1,9 +1,7 @@
import { Editor, mergeAttributes } from "@tiptap/core";
import { Image } from "@tiptap/extension-image";
import { ReactNodeViewRenderer } from "@tiptap/react";
import { v4 as uuidv4 } from "uuid";
// extensions
import { CustomImageNode } from "@/extensions/custom-image";
import { CustomImageExtensionConfig, CustomImageNode, getImageComponentImageFileMap } from "@/extensions/custom-image";
// plugins
import { TrackImageDeletionPlugin, TrackImageRestorationPlugin, isFileValid } from "@/plugins/image";
// types
@@ -27,80 +25,18 @@ declare module "@tiptap/core" {
}
}
export const getImageComponentImageFileMap = (editor: Editor) =>
(editor.storage.imageComponent as UploadImageExtensionStorage | undefined)?.fileMap;
export interface UploadImageExtensionStorage {
fileMap: Map<string, UploadEntity>;
}
export type UploadEntity = ({ event: "insert" } | { event: "drop"; file: File }) & { hasOpenedFileInputOnce?: boolean };
export const CustomImageExtension = (props: TFileHandler) => {
const {
getAssetSrc,
upload,
delete: deleteImage,
restore: restoreImage,
validation: { maxFileSize },
} = props;
return Image.extend<Record<string, unknown>, UploadImageExtensionStorage>({
name: "imageComponent",
return CustomImageExtensionConfig(props).extend({
selectable: true,
group: "block",
atom: true,
draggable: true,
addAttributes() {
return {
...this.parent?.(),
width: {
default: "35%",
},
src: {
default: null,
},
height: {
default: "auto",
},
["id"]: {
default: null,
},
aspectRatio: {
default: null,
},
};
},
parseHTML() {
return [
{
tag: "image-component",
},
];
},
renderHTML({ HTMLAttributes }) {
return ["image-component", mergeAttributes(HTMLAttributes)];
},
onCreate(this) {
const imageSources = new Set<string>();
this.editor.state.doc.descendants((node) => {
if (node.type.name === this.name) {
imageSources.add(node.attrs.src);
}
});
imageSources.forEach(async (src) => {
try {
await restoreImage(src);
} catch (error) {
console.error("Error restoring image: ", error);
}
});
},
addKeyboardShortcuts() {
return {
ArrowDown: insertEmptyParagraphAtNodeBoundaries("down", this.name),
@@ -115,15 +51,6 @@ export const CustomImageExtension = (props: TFileHandler) => {
];
},
addStorage() {
return {
fileMap: new Map(),
deletedImageSet: new Map<string, boolean>(),
uploadInProgress: false,
maxFileSize,
};
},
addCommands() {
return {
insertImageComponent:
@@ -179,7 +106,6 @@ export const CustomImageExtension = (props: TFileHandler) => {
const fileUrl = await upload(file);
return fileUrl;
},
getImageSource: (path: string) => () => getAssetSrc(path),
};
},

View File

@@ -0,0 +1,101 @@
import { Editor, mergeAttributes } from "@tiptap/core";
import { Image } from "@tiptap/extension-image";
// types
import { TFileHandler, TReadOnlyFileHandler } from "@/types";
export const getImageComponentImageFileMap = (editor: Editor) =>
(editor.storage.imageComponent as UploadImageExtensionStorage | undefined)?.fileMap;
export interface UploadImageExtensionStorage {
fileMap: Map<string, UploadEntity>;
}
export type UploadEntity = ({ event: "insert" } | { event: "drop"; file: File }) & { hasOpenedFileInputOnce?: boolean };
type Props = TReadOnlyFileHandler & Pick<TFileHandler, "validation">;
const fallbackProps: Props = {
getAssetSrc: () => "",
restore: async () => {},
validation: {
maxFileSize: 0,
},
};
export const CustomImageExtensionConfig = (props: Props = fallbackProps) => {
const {
getAssetSrc,
restore: restoreImage,
validation: { maxFileSize },
} = props;
return Image.extend<Record<string, unknown>, UploadImageExtensionStorage>({
name: "imageComponent",
group: "block",
atom: true,
addAttributes() {
return {
...this.parent?.(),
width: {
default: "35%",
},
src: {
default: null,
},
height: {
default: "auto",
},
["id"]: {
default: null,
},
aspectRatio: {
default: null,
},
};
},
parseHTML() {
return [
{
tag: "image-component",
},
];
},
renderHTML({ HTMLAttributes }) {
return ["image-component", mergeAttributes(HTMLAttributes)];
},
onCreate(this) {
const imageSources = new Set<string>();
this.editor.state.doc.descendants((node) => {
if (node.type.name === this.name) {
imageSources.add(node.attrs.src);
}
});
imageSources.forEach(async (src) => {
try {
await restoreImage(src);
} catch (error) {
console.error("Error restoring image: ", error);
}
});
},
addStorage() {
return {
fileMap: new Map(),
deletedImageSet: new Map<string, boolean>(),
uploadInProgress: false,
maxFileSize,
};
},
addCommands() {
return {
getImageSource: (path: string) => () => getAssetSrc(path),
};
},
});
};

View File

@@ -1,3 +1,4 @@
export * from "./components";
export * from "./custom-image";
export * from "./extension-config";
export * from "./read-only-custom-image";

View File

@@ -1,68 +1,20 @@
import { mergeAttributes } from "@tiptap/core";
import { Image } from "@tiptap/extension-image";
import { ReactNodeViewRenderer } from "@tiptap/react";
// components
import { CustomImageNode, UploadImageExtensionStorage } from "@/extensions/custom-image";
import { CustomImageExtensionConfig, CustomImageNode } from "@/extensions/custom-image";
// types
import { TFileHandler } from "@/types";
import { TReadOnlyFileHandler } from "@/types";
export const CustomReadOnlyImageExtension = (props: Pick<TFileHandler, "getAssetSrc">) => {
const { getAssetSrc } = props;
return Image.extend<Record<string, unknown>, UploadImageExtensionStorage>({
name: "imageComponent",
export const CustomReadOnlyImageExtension = (props: TReadOnlyFileHandler) =>
CustomImageExtensionConfig({
...props,
validation: {
maxFileSize: 5 * 1024 * 1024,
},
}).extend({
selectable: false,
group: "block",
atom: true,
draggable: false,
addAttributes() {
return {
...this.parent?.(),
width: {
default: "35%",
},
src: {
default: null,
},
height: {
default: "auto",
},
["id"]: {
default: null,
},
aspectRatio: {
default: null,
},
};
},
parseHTML() {
return [
{
tag: "image-component",
},
];
},
renderHTML({ HTMLAttributes }) {
return ["image-component", mergeAttributes(HTMLAttributes)];
},
addStorage() {
return {
fileMap: new Map(),
};
},
addCommands() {
return {
getImageSource: (path: string) => () => getAssetSrc(path),
};
},
addNodeView() {
return ReactNodeViewRenderer(CustomImageNode);
},
});
};

View File

@@ -1,55 +0,0 @@
import { mergeAttributes } from "@tiptap/core";
import { Image } from "@tiptap/extension-image";
// extensions
import { UploadImageExtensionStorage } from "@/extensions";
export const CustomImageComponentWithoutProps = () =>
Image.extend<Record<string, unknown>, UploadImageExtensionStorage>({
name: "imageComponent",
selectable: true,
group: "block",
atom: true,
draggable: true,
addAttributes() {
return {
...this.parent?.(),
width: {
default: "35%",
},
src: {
default: null,
},
height: {
default: "auto",
},
["id"]: {
default: null,
},
aspectRatio: {
default: null,
},
};
},
parseHTML() {
return [
{
tag: "image-component",
},
];
},
renderHTML({ HTMLAttributes }) {
return ["image-component", mergeAttributes(HTMLAttributes)];
},
addStorage() {
return {
fileMap: new Map(),
deletedImageSet: new Map<string, boolean>(),
};
},
});
export default CustomImageComponentWithoutProps;

View File

@@ -3,9 +3,9 @@ import { ReactNodeViewRenderer } from "@tiptap/react";
// extensions
import { CustomImageNode } from "@/extensions";
// types
import { TFileHandler } from "@/types";
import { TReadOnlyFileHandler } from "@/types";
export const ReadOnlyImageExtension = (props: Pick<TFileHandler, "getAssetSrc">) => {
export const ReadOnlyImageExtension = (props: TReadOnlyFileHandler) => {
const { getAssetSrc } = props;
return Image.extend({

View File

@@ -26,10 +26,10 @@ import {
// helpers
import { isValidHttpUrl } from "@/helpers/common";
// types
import { IMentionHighlight, TFileHandler } from "@/types";
import { IMentionHighlight, TReadOnlyFileHandler } from "@/types";
type Props = {
fileHandler: Pick<TFileHandler, "getAssetSrc">;
fileHandler: TReadOnlyFileHandler;
mentionConfig: {
mentionHighlights?: () => Promise<IMentionHighlight[]>;
};
@@ -82,6 +82,7 @@ export const CoreReadOnlyEditorExtensions = (props: Props) => {
CustomTypographyExtension,
ReadOnlyImageExtension({
getAssetSrc: fileHandler.getAssetSrc,
restore: fileHandler.restore,
}).configure({
HTMLAttributes: {
class: "rounded-md",
@@ -89,6 +90,7 @@ export const CoreReadOnlyEditorExtensions = (props: Props) => {
}),
CustomReadOnlyImageExtension({
getAssetSrc: fileHandler.getAssetSrc,
restore: fileHandler.restore,
}),
TiptapUnderline,
TextStyle,

View File

@@ -11,7 +11,7 @@ import { IMarking, scrollSummary } from "@/helpers/scroll-to-node";
// props
import { CoreReadOnlyEditorProps } from "@/props";
// types
import { EditorReadOnlyRefApi, IMentionHighlight, TFileHandler } from "@/types";
import { EditorReadOnlyRefApi, IMentionHighlight, TReadOnlyFileHandler } from "@/types";
interface CustomReadOnlyEditorProps {
initialValue?: string;
@@ -19,7 +19,7 @@ interface CustomReadOnlyEditorProps {
forwardedRef?: MutableRefObject<EditorReadOnlyRefApi | null>;
extensions?: any;
editorProps?: EditorProps;
fileHandler: Pick<TFileHandler, "getAssetSrc">;
fileHandler: TReadOnlyFileHandler;
handleEditorReady?: (value: boolean) => void;
mentionHandler: {
highlights: () => Promise<IMentionHighlight[]>;

View File

@@ -10,6 +10,7 @@ import {
IMentionSuggestion,
TExtensions,
TFileHandler,
TReadOnlyFileHandler,
TRealtimeConfig,
TUserDetails,
} from "@/types";
@@ -44,6 +45,6 @@ export type TCollaborativeEditorProps = TCollaborativeEditorHookProps & {
};
export type TReadOnlyCollaborativeEditorProps = TCollaborativeEditorHookProps & {
fileHandler: Pick<TFileHandler, "getAssetSrc">;
fileHandler: TReadOnlyFileHandler;
forwardedRef?: React.MutableRefObject<EditorReadOnlyRefApi | null>;
};

View File

@@ -1,11 +1,14 @@
import { DeleteImage, RestoreImage, UploadImage } from "@/types";
export type TFileHandler = {
export type TReadOnlyFileHandler = {
getAssetSrc: (path: string) => string;
restore: RestoreImage;
};
export type TFileHandler = TReadOnlyFileHandler & {
cancel: () => void;
delete: DeleteImage;
upload: UploadImage;
restore: RestoreImage;
validation: {
/**
* @description max file size in bytes

View File

@@ -13,6 +13,7 @@ import {
TExtensions,
TFileHandler,
TNonColorEditorCommands,
TReadOnlyFileHandler,
TServerHandler,
} from "@/types";
// editor refs
@@ -108,7 +109,7 @@ export interface IReadOnlyEditorProps {
containerClassName?: string;
displayConfig?: TDisplayConfig;
editorClassName?: string;
fileHandler: Pick<TFileHandler, "getAssetSrc">;
fileHandler: TReadOnlyFileHandler;
forwardedRef?: React.MutableRefObject<EditorReadOnlyRefApi | null>;
id: string;
initialValue: string;

View File

@@ -9,10 +9,11 @@ import { useMention } from "@/hooks/use-mention";
type LiteTextReadOnlyEditorWrapperProps = Omit<ILiteTextReadOnlyEditor, "fileHandler" | "mentionHandler"> & {
anchor: string;
workspaceId: string;
};
export const LiteTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, LiteTextReadOnlyEditorWrapperProps>(
({ anchor, ...props }, ref) => {
({ anchor, workspaceId, ...props }, ref) => {
const { mentionHighlights } = useMention();
return (
@@ -20,6 +21,7 @@ export const LiteTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, Lit
ref={ref}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
workspaceId,
})}
mentionHandler={{
highlights: mentionHighlights,

View File

@@ -9,10 +9,11 @@ import { useMention } from "@/hooks/use-mention";
type RichTextReadOnlyEditorWrapperProps = Omit<IRichTextReadOnlyEditor, "fileHandler" | "mentionHandler"> & {
anchor: string;
workspaceId: string;
};
export const RichTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, RichTextReadOnlyEditorWrapperProps>(
({ anchor, ...props }, ref) => {
({ anchor, workspaceId, ...props }, ref) => {
const { mentionHighlights } = useMention();
return (
@@ -20,6 +21,7 @@ export const RichTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, Ric
ref={ref}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
workspaceId,
})}
mentionHandler={{ highlights: mentionHighlights }}
{...props}

View File

@@ -140,6 +140,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
<div className={`${isEditing ? "hidden" : ""}`}>
<LiteTextReadOnlyEditor
anchor={anchor}
workspaceId={workspaceID?.toString() ?? ""}
ref={showEditorRef}
id={comment.id}
initialValue={comment.comment_html}

View File

@@ -14,7 +14,7 @@ type Props = {
export const PeekOverviewIssueDetails: React.FC<Props> = observer((props) => {
const { anchor, issueDetails } = props;
const { project_details } = usePublish(anchor);
const { project_details, workspace: workspaceId } = usePublish(anchor);
const description = issueDetails.description_html;
@@ -27,6 +27,7 @@ export const PeekOverviewIssueDetails: React.FC<Props> = observer((props) => {
{description !== "" && description !== "<p></p>" && (
<RichTextReadOnlyEditor
anchor={anchor}
workspaceId={workspaceId?.toString() ?? ""}
id={issueDetails.id}
initialValue={
!description ||

View File

@@ -1,5 +1,5 @@
// plane editor
import { TFileHandler } from "@plane/editor";
import { TFileHandler, TReadOnlyFileHandler } from "@plane/editor";
// constants
import { MAX_FILE_SIZE } from "@/constants/common";
// helpers
@@ -25,11 +25,10 @@ type TArgs = {
};
/**
* @description this function returns the file handler required by the editors
* @param {TArgs} args
* @description this function returns the file handler required by the read-only editors
*/
export const getEditorFileHandlers = (args: TArgs): TFileHandler => {
const { anchor, uploadFile, workspaceId } = args;
export const getReadOnlyEditorFileHandlers = (args: Pick<TArgs, "anchor" | "workspaceId">): TReadOnlyFileHandler => {
const { anchor, workspaceId } = args;
return {
getAssetSrc: (path) => {
@@ -40,14 +39,6 @@ export const getEditorFileHandlers = (args: TArgs): TFileHandler => {
return getEditorAssetSrc(anchor, path) ?? "";
}
},
upload: uploadFile,
delete: async (src: string) => {
if (checkURLValidity(src)) {
await fileService.deleteOldEditorAsset(workspaceId, src);
} else {
await fileService.deleteNewAsset(getEditorAssetSrc(anchor, src) ?? "");
}
},
restore: async (src: string) => {
if (checkURLValidity(src)) {
await fileService.restoreOldEditorAsset(workspaceId, src);
@@ -55,29 +46,32 @@ export const getEditorFileHandlers = (args: TArgs): TFileHandler => {
await fileService.restoreNewAsset(anchor, src);
}
},
};
};
/**
* @description this function returns the file handler required by the editors
* @param {TArgs} args
*/
export const getEditorFileHandlers = (args: TArgs): TFileHandler => {
const { anchor, uploadFile, workspaceId } = args;
return {
...getReadOnlyEditorFileHandlers({
anchor,
workspaceId,
}),
upload: uploadFile,
delete: async (src: string) => {
if (checkURLValidity(src)) {
await fileService.deleteOldEditorAsset(workspaceId, src);
} else {
await fileService.deleteNewAsset(getEditorAssetSrc(anchor, src) ?? "");
}
},
cancel: fileService.cancelUpload,
validation: {
maxFileSize: MAX_FILE_SIZE,
},
};
};
/**
* @description this function returns the file handler required by the read-only editors
*/
export const getReadOnlyEditorFileHandlers = (
args: Pick<TArgs, "anchor">
): { getAssetSrc: TFileHandler["getAssetSrc"] } => {
const { anchor } = args;
return {
getAssetSrc: (path) => {
if (!path) return "";
if (checkURLValidity(path)) {
return path;
} else {
return getEditorAssetSrc(anchor, path) ?? "";
}
},
};
};

View File

@@ -5,7 +5,7 @@ import { EditorReadOnlyRefApi, ILiteTextReadOnlyEditor, LiteTextReadOnlyEditorWi
import { cn } from "@/helpers/common.helper";
import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
// hooks
import { useMention, useUser } from "@/hooks/store";
import { useMention, useUser, useWorkspace } from "@/hooks/store";
type LiteTextReadOnlyEditorWrapperProps = Omit<ILiteTextReadOnlyEditor, "fileHandler" | "mentionHandler"> & {
workspaceSlug: string;
@@ -19,12 +19,16 @@ export const LiteTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, Lit
const { mentionHighlights } = useMention({
user: currentUser,
});
const { getWorkspaceBySlug } = useWorkspace();
// derived values
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id ?? "";
return (
<LiteTextReadOnlyEditorWithRef
ref={ref}
fileHandler={getReadOnlyEditorFileHandlers({
projectId,
workspaceId,
workspaceSlug,
})}
mentionHandler={{

View File

@@ -5,7 +5,7 @@ import { EditorReadOnlyRefApi, IRichTextReadOnlyEditor, RichTextReadOnlyEditorWi
import { cn } from "@/helpers/common.helper";
import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
// hooks
import { useMention } from "@/hooks/store";
import { useMention, useWorkspace } from "@/hooks/store";
type RichTextReadOnlyEditorWrapperProps = Omit<IRichTextReadOnlyEditor, "fileHandler" | "mentionHandler"> & {
workspaceSlug: string;
@@ -14,13 +14,18 @@ type RichTextReadOnlyEditorWrapperProps = Omit<IRichTextReadOnlyEditor, "fileHan
export const RichTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, RichTextReadOnlyEditorWrapperProps>(
({ workspaceSlug, projectId, ...props }, ref) => {
// store hooks
const { getWorkspaceBySlug } = useWorkspace();
const { mentionHighlights } = useMention({});
// derived values
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id ?? "";
return (
<RichTextReadOnlyEditorWithRef
ref={ref}
fileHandler={getReadOnlyEditorFileHandlers({
projectId,
workspaceId,
workspaceSlug,
})}
mentionHandler={{

View File

@@ -54,7 +54,7 @@ export const IssueCommentCard: FC<TIssueCommentCard> = observer((props) => {
const comment = getCommentById(commentId);
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(comment?.workspace_detail?.slug as string)?.id as string;
const workspaceId = workspaceStore.getWorkspaceBySlug(comment?.workspace_detail?.slug ?? "")?.id;
const {
formState: { isSubmitting },
@@ -143,7 +143,7 @@ export const IssueCommentCard: FC<TIssueCommentCard> = observer((props) => {
}}
>
<LiteTextEditor
workspaceId={workspaceId}
workspaceId={workspaceId ?? ""}
projectId={projectId}
workspaceSlug={workspaceSlug}
ref={editorRef}

View File

@@ -37,7 +37,7 @@ export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
const workspaceStore = useWorkspace();
const { peekIssue } = useIssueDetail();
// derived values
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug ?? "")?.id;
// form info
const {
handleSubmit,
@@ -92,7 +92,7 @@ export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
control={control}
render={({ field: { value, onChange } }) => (
<LiteTextEditor
workspaceId={workspaceId}
workspaceId={workspaceId ?? ""}
id={"add_comment_" + issueId}
value={"<p></p>"}
projectId={projectId}

View File

@@ -230,6 +230,7 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
ref={readOnlyEditorRef}
fileHandler={getReadOnlyEditorFileHandlers({
projectId: projectId?.toString() ?? "",
workspaceId,
workspaceSlug: workspaceSlug?.toString() ?? "",
})}
handleEditorReady={handleReadOnlyEditorReady}

View File

@@ -9,7 +9,7 @@ import { Loader } from "@plane/ui";
// helpers
import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
// hooks
import { useMember, useMention, useUser } from "@/hooks/store";
import { useMember, useMention, useUser, useWorkspace } from "@/hooks/store";
import { usePageFilters } from "@/hooks/use-page-filters";
// plane web hooks
import { useIssueEmbed } from "@/plane-web/hooks/use-issue-embed";
@@ -31,9 +31,11 @@ export const PagesVersionEditor: React.FC<TVersionEditorProps> = observer((props
getUserDetails,
project: { getProjectMemberIds },
} = useMember();
const { getWorkspaceBySlug } = useWorkspace();
// derived values
const projectMemberIds = projectId ? getProjectMemberIds(projectId.toString()) : [];
const projectMemberDetails = projectMemberIds?.map((id) => getUserDetails(id) as IUserLite);
const workspaceId = getWorkspaceBySlug(workspaceSlug?.toString() ?? "")?.id ?? "";
// issue-embed
const { issueEmbedProps } = useIssueEmbed(workspaceSlug?.toString() ?? "", projectId?.toString() ?? "");
// use-mention
@@ -105,6 +107,7 @@ export const PagesVersionEditor: React.FC<TVersionEditorProps> = observer((props
editorClassName="pl-10"
fileHandler={getReadOnlyEditorFileHandlers({
projectId: projectId?.toString() ?? "",
workspaceId,
workspaceSlug: workspaceSlug?.toString() ?? "",
})}
mentionHandler={{

View File

@@ -1,5 +1,5 @@
// plane editor
import { TFileHandler } from "@plane/editor";
import { TFileHandler, TReadOnlyFileHandler } from "@plane/editor";
// helpers
import { getBase64Image, getFileURL } from "@/helpers/file.helper";
import { checkURLValidity } from "@/helpers/string.helper";
@@ -37,11 +37,12 @@ type TArgs = {
};
/**
* @description this function returns the file handler required by the editors
* @param {TArgs} args
* @description this function returns the file handler required by the read-only editors
*/
export const getEditorFileHandlers = (args: TArgs): TFileHandler => {
const { maxFileSize, projectId, uploadFile, workspaceId, workspaceSlug } = args;
export const getReadOnlyEditorFileHandlers = (
args: Pick<TArgs, "projectId" | "workspaceId" | "workspaceSlug">
): TReadOnlyFileHandler => {
const { projectId, workspaceId, workspaceSlug } = args;
return {
getAssetSrc: (path) => {
@@ -58,6 +59,29 @@ export const getEditorFileHandlers = (args: TArgs): TFileHandler => {
);
}
},
restore: async (src: string) => {
if (checkURLValidity(src)) {
await fileService.restoreOldEditorAsset(workspaceId, src);
} else {
await fileService.restoreNewAsset(workspaceSlug, src);
}
},
};
};
/**
* @description this function returns the file handler required by the editors
* @param {TArgs} args
*/
export const getEditorFileHandlers = (args: TArgs): TFileHandler => {
const { maxFileSize, projectId, uploadFile, workspaceId, workspaceSlug } = args;
return {
...getReadOnlyEditorFileHandlers({
projectId,
workspaceId,
workspaceSlug,
}),
upload: uploadFile,
delete: async (src: string) => {
if (checkURLValidity(src)) {
@@ -72,13 +96,6 @@ export const getEditorFileHandlers = (args: TArgs): TFileHandler => {
);
}
},
restore: async (src: string) => {
if (checkURLValidity(src)) {
await fileService.restoreOldEditorAsset(workspaceId, src);
} else {
await fileService.restoreNewAsset(workspaceSlug, src);
}
},
cancel: fileService.cancelUpload,
validation: {
maxFileSize,
@@ -86,32 +103,6 @@ export const getEditorFileHandlers = (args: TArgs): TFileHandler => {
};
};
/**
* @description this function returns the file handler required by the read-only editors
*/
export const getReadOnlyEditorFileHandlers = (
args: Pick<TArgs, "projectId" | "workspaceSlug">
): { getAssetSrc: TFileHandler["getAssetSrc"] } => {
const { projectId, workspaceSlug } = args;
return {
getAssetSrc: (path) => {
if (!path) return "";
if (checkURLValidity(path)) {
return path;
} else {
return (
getEditorAssetSrc({
assetId: path,
projectId,
workspaceSlug,
}) ?? ""
);
}
},
};
};
/**
* @description function to replace all the custom components from the html component to make it pdf compatible
* @param props