mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
[WIKI-537] refactor: document editor (#7384)
* refactor: document editor * chore: update user prop * fix: type warning * chore: update value prop name * chore: remove unnecessary exports * hore: update initialValue type * chore: revert initialValue type * refactor: unnecessary string handlers
This commit is contained in:
committed by
GitHub
parent
e20bfa55d6
commit
27f74206a3
92
apps/web/core/components/editor/document/editor.tsx
Normal file
92
apps/web/core/components/editor/document/editor.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import React, { forwardRef } from "react";
|
||||
// plane imports
|
||||
import { DocumentEditorWithRef, EditorRefApi, IDocumentEditorProps, TFileHandler } from "@plane/editor";
|
||||
import { MakeOptional, TSearchEntityRequestPayload, TSearchResponse } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { EditorMentionsRoot } from "@/components/editor";
|
||||
// hooks
|
||||
import { useEditorConfig, useEditorMention } from "@/hooks/editor";
|
||||
import { useMember } from "@/hooks/store";
|
||||
// plane web hooks
|
||||
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" | "user">,
|
||||
"disabledExtensions" | "editable" | "flaggedExtensions"
|
||||
> & {
|
||||
embedHandler?: Partial<IDocumentEditorProps["embedHandler"]>;
|
||||
workspaceSlug: string;
|
||||
workspaceId: string;
|
||||
projectId?: string;
|
||||
} & (
|
||||
| {
|
||||
editable: false;
|
||||
}
|
||||
| {
|
||||
editable: true;
|
||||
searchMentionCallback: (payload: TSearchEntityRequestPayload) => Promise<TSearchResponse>;
|
||||
uploadFile: TFileHandler["upload"];
|
||||
}
|
||||
);
|
||||
|
||||
export const DocumentEditor = forwardRef<EditorRefApi, DocumentEditorWrapperProps>((props, ref) => {
|
||||
const {
|
||||
containerClassName,
|
||||
editable,
|
||||
embedHandler,
|
||||
workspaceSlug,
|
||||
workspaceId,
|
||||
projectId,
|
||||
disabledExtensions: additionalDisabledExtensions = [],
|
||||
...rest
|
||||
} = props;
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
// editor flaggings
|
||||
const { document: documentEditorExtensions } = useEditorFlagging(workspaceSlug);
|
||||
// use editor mention
|
||||
const { fetchMentions } = useEditorMention({
|
||||
searchEntity: editable ? async (payload) => await props.searchMentionCallback(payload) : async () => ({}),
|
||||
});
|
||||
// editor config
|
||||
const { getEditorFileHandlers } = useEditorConfig();
|
||||
// issue-embed
|
||||
const { issueEmbedProps } = useIssueEmbed({
|
||||
projectId,
|
||||
workspaceSlug,
|
||||
});
|
||||
|
||||
return (
|
||||
<DocumentEditorWithRef
|
||||
ref={ref}
|
||||
disabledExtensions={[...documentEditorExtensions.disabled, ...(additionalDisabledExtensions ?? [])]}
|
||||
editable={editable}
|
||||
flaggedExtensions={documentEditorExtensions.flagged}
|
||||
fileHandler={getEditorFileHandlers({
|
||||
projectId,
|
||||
uploadFile: editable ? props.uploadFile : async () => "",
|
||||
workspaceId,
|
||||
workspaceSlug,
|
||||
})}
|
||||
mentionHandler={{
|
||||
searchCallback: async (query) => {
|
||||
const res = await fetchMentions(query);
|
||||
if (!res) throw new Error("Failed in fetching mentions");
|
||||
return res;
|
||||
},
|
||||
renderComponent: EditorMentionsRoot,
|
||||
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
|
||||
}}
|
||||
embedHandler={{
|
||||
issue: issueEmbedProps,
|
||||
...embedHandler,
|
||||
}}
|
||||
{...rest}
|
||||
containerClassName={cn("relative pl-3 pb-3", containerClassName)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
DocumentEditor.displayName = "DocumentEditor";
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from "./embeds";
|
||||
export * from "./lite-text-editor";
|
||||
export * from "./lite-text";
|
||||
export * from "./pdf";
|
||||
export * from "./rich-text-editor";
|
||||
export * from "./rich-text";
|
||||
export * from "./sticky-editor";
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from "./lite-text-editor";
|
||||
export * from "./lite-text-read-only-editor";
|
||||
export * from "./toolbar";
|
||||
3
apps/web/core/components/editor/lite-text/index.ts
Normal file
3
apps/web/core/components/editor/lite-text/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./editor";
|
||||
export * from "./read-only-editor";
|
||||
export * from "./toolbar";
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./rich-text-editor";
|
||||
1
apps/web/core/components/editor/rich-text/index.ts
Normal file
1
apps/web/core/components/editor/rich-text/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./editor";
|
||||
@@ -10,7 +10,7 @@ import { EFileAssetType, TIssue } from "@plane/types";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { getDescriptionPlaceholderI18n, getTabIndex } from "@plane/utils";
|
||||
// components
|
||||
import { RichTextEditor } from "@/components/editor/rich-text-editor/rich-text-editor";
|
||||
import { RichTextEditor } from "@/components/editor/rich-text/editor";
|
||||
// hooks
|
||||
import { useEditorAsset, useProjectInbox } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { DocumentReadOnlyEditorWithRef, TDisplayConfig } from "@plane/editor";
|
||||
import { TDisplayConfig } from "@plane/editor";
|
||||
import { TPageVersion } from "@plane/types";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { EditorMentionsRoot } from "@/components/editor";
|
||||
import { DocumentEditor } from "@/components/editor/document/editor";
|
||||
// hooks
|
||||
import { useEditorConfig } from "@/hooks/editor";
|
||||
import { useMember, useWorkspace } from "@/hooks/store";
|
||||
import { useWorkspace } from "@/hooks/store";
|
||||
import { usePageFilters } from "@/hooks/use-page-filters";
|
||||
// plane web hooks
|
||||
import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging";
|
||||
import { useIssueEmbed } from "@/plane-web/hooks/use-issue-embed";
|
||||
|
||||
export type TVersionEditorProps = {
|
||||
activeVersion: string | null;
|
||||
@@ -21,23 +17,12 @@ export type TVersionEditorProps = {
|
||||
|
||||
export const PagesVersionEditor: React.FC<TVersionEditorProps> = observer((props) => {
|
||||
const { activeVersion, versionDetails } = props;
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
// params
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store hooks
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
// derived values
|
||||
const workspaceDetails = getWorkspaceBySlug(workspaceSlug?.toString() ?? "");
|
||||
// editor flaggings
|
||||
const { document: documentEditorExtensions } = useEditorFlagging(workspaceSlug?.toString() ?? "");
|
||||
// editor config
|
||||
const { getReadOnlyEditorFileHandlers } = useEditorConfig();
|
||||
// issue-embed
|
||||
const { issueEmbedProps } = useIssueEmbed({
|
||||
projectId: projectId?.toString() ?? "",
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
});
|
||||
// page filters
|
||||
const { fontSize, fontStyle } = usePageFilters();
|
||||
|
||||
@@ -89,32 +74,21 @@ export const PagesVersionEditor: React.FC<TVersionEditorProps> = observer((props
|
||||
</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
|
||||
key={activeVersion ?? ""}
|
||||
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({
|
||||
projectId: projectId?.toString() ?? "",
|
||||
workspaceId: workspaceDetails?.id ?? "",
|
||||
workspaceSlug: workspaceSlug?.toString() ?? "",
|
||||
})}
|
||||
mentionHandler={{
|
||||
renderComponent: (props) => <EditorMentionsRoot {...props} />,
|
||||
getMentionedEntityDetails: (id: string) => ({ display_name: getUserDetails(id)?.display_name ?? "" }),
|
||||
}}
|
||||
embedHandler={{
|
||||
issue: {
|
||||
widgetCallback: issueEmbedProps.widgetCallback,
|
||||
},
|
||||
}}
|
||||
projectId={projectId?.toString()}
|
||||
workspaceId={workspaceDetails?.id ?? ""}
|
||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ export type TDocumentEditorAdditionalExtensionsProps = Pick<
|
||||
"disabledExtensions" | "flaggedExtensions" | "fileHandler"
|
||||
> & {
|
||||
embedConfig: TEmbedConfig | undefined;
|
||||
isEditable: boolean;
|
||||
provider?: HocuspocusProvider;
|
||||
userDetails: TUserDetails;
|
||||
};
|
||||
|
||||
109
packages/editor/src/core/components/editors/document/editor.tsx
Normal file
109
packages/editor/src/core/components/editors/document/editor.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Extensions } from "@tiptap/core";
|
||||
import { forwardRef, MutableRefObject, useMemo } from "react";
|
||||
// plane imports
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { PageRenderer } from "@/components/editors";
|
||||
// constants
|
||||
import { DEFAULT_DISPLAY_CONFIG } from "@/constants/config";
|
||||
// extensions
|
||||
import { HeadingListExtension, WorkItemEmbedExtension, SideMenuExtension } from "@/extensions";
|
||||
// helpers
|
||||
import { getEditorClassNames } from "@/helpers/common";
|
||||
// hooks
|
||||
import { useEditor } from "@/hooks/use-editor";
|
||||
// plane editor extensions
|
||||
import { DocumentEditorAdditionalExtensions } from "@/plane-editor/extensions";
|
||||
// types
|
||||
import { EditorRefApi, IDocumentEditorProps } from "@/types";
|
||||
|
||||
const DocumentEditor = (props: IDocumentEditorProps) => {
|
||||
const {
|
||||
bubbleMenuEnabled = false,
|
||||
containerClassName,
|
||||
disabledExtensions,
|
||||
displayConfig = DEFAULT_DISPLAY_CONFIG,
|
||||
editable,
|
||||
editorClassName = "",
|
||||
embedHandler,
|
||||
fileHandler,
|
||||
flaggedExtensions,
|
||||
forwardedRef,
|
||||
id,
|
||||
handleEditorReady,
|
||||
mentionHandler,
|
||||
onChange,
|
||||
user,
|
||||
value,
|
||||
} = props;
|
||||
const extensions: Extensions = useMemo(() => {
|
||||
const additionalExtensions: Extensions = [];
|
||||
if (embedHandler?.issue) {
|
||||
additionalExtensions.push(
|
||||
WorkItemEmbedExtension({
|
||||
widgetCallback: embedHandler.issue.widgetCallback,
|
||||
})
|
||||
);
|
||||
}
|
||||
additionalExtensions.push(
|
||||
SideMenuExtension({
|
||||
aiEnabled: !disabledExtensions?.includes("ai"),
|
||||
dragDropEnabled: true,
|
||||
}),
|
||||
HeadingListExtension,
|
||||
...DocumentEditorAdditionalExtensions({
|
||||
disabledExtensions,
|
||||
embedConfig: embedHandler,
|
||||
flaggedExtensions,
|
||||
isEditable: editable,
|
||||
fileHandler,
|
||||
userDetails: user ?? {
|
||||
id: "",
|
||||
name: "",
|
||||
color: "",
|
||||
},
|
||||
})
|
||||
);
|
||||
return additionalExtensions;
|
||||
}, []);
|
||||
|
||||
const editor = useEditor({
|
||||
disabledExtensions,
|
||||
editable,
|
||||
editorClassName,
|
||||
enableHistory: true,
|
||||
extensions,
|
||||
fileHandler,
|
||||
flaggedExtensions,
|
||||
forwardedRef,
|
||||
handleEditorReady,
|
||||
id,
|
||||
initialValue: value,
|
||||
mentionHandler,
|
||||
onChange,
|
||||
});
|
||||
|
||||
const editorContainerClassName = getEditorClassNames({
|
||||
containerClassName,
|
||||
});
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
return (
|
||||
<PageRenderer
|
||||
bubbleMenuEnabled={bubbleMenuEnabled}
|
||||
displayConfig={displayConfig}
|
||||
editor={editor}
|
||||
editorContainerClassName={cn(editorContainerClassName, "document-editor")}
|
||||
id={id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DocumentEditorWithRef = forwardRef<EditorRefApi, IDocumentEditorProps>((props, ref) => (
|
||||
<DocumentEditor {...props} forwardedRef={ref as MutableRefObject<EditorRefApi | null>} />
|
||||
));
|
||||
|
||||
DocumentEditorWithRef.displayName = "DocumentEditorWithRef";
|
||||
|
||||
export { DocumentEditorWithRef };
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from "./collaborative-editor";
|
||||
export * from "./editor";
|
||||
export * from "./loader";
|
||||
export * from "./page-renderer";
|
||||
export * from "./read-only-editor";
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import { Extensions } from "@tiptap/core";
|
||||
import React, { forwardRef, MutableRefObject } from "react";
|
||||
// plane imports
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { PageRenderer } from "@/components/editors";
|
||||
// constants
|
||||
import { DEFAULT_DISPLAY_CONFIG } from "@/constants/config";
|
||||
// extensions
|
||||
import { WorkItemEmbedExtension } from "@/extensions";
|
||||
// helpers
|
||||
import { getEditorClassNames } from "@/helpers/common";
|
||||
// hooks
|
||||
import { useReadOnlyEditor } from "@/hooks/use-read-only-editor";
|
||||
// types
|
||||
import { EditorReadOnlyRefApi, IDocumentReadOnlyEditorProps } from "@/types";
|
||||
|
||||
const DocumentReadOnlyEditor: React.FC<IDocumentReadOnlyEditorProps> = (props) => {
|
||||
const {
|
||||
containerClassName,
|
||||
disabledExtensions,
|
||||
displayConfig = DEFAULT_DISPLAY_CONFIG,
|
||||
editorClassName = "",
|
||||
embedHandler,
|
||||
fileHandler,
|
||||
flaggedExtensions,
|
||||
id,
|
||||
forwardedRef,
|
||||
handleEditorReady,
|
||||
initialValue,
|
||||
mentionHandler,
|
||||
} = props;
|
||||
const extensions: Extensions = [];
|
||||
if (embedHandler?.issue) {
|
||||
extensions.push(
|
||||
WorkItemEmbedExtension({
|
||||
widgetCallback: embedHandler.issue.widgetCallback,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const editor = useReadOnlyEditor({
|
||||
disabledExtensions,
|
||||
editorClassName,
|
||||
extensions,
|
||||
fileHandler,
|
||||
flaggedExtensions,
|
||||
forwardedRef,
|
||||
handleEditorReady,
|
||||
initialValue,
|
||||
mentionHandler,
|
||||
});
|
||||
|
||||
const editorContainerClassName = getEditorClassNames({
|
||||
containerClassName,
|
||||
});
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
return (
|
||||
<PageRenderer
|
||||
bubbleMenuEnabled={false}
|
||||
displayConfig={displayConfig}
|
||||
editor={editor}
|
||||
editorContainerClassName={cn(editorContainerClassName, "document-editor")}
|
||||
id={id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DocumentReadOnlyEditorWithRef = forwardRef<EditorReadOnlyRefApi, IDocumentReadOnlyEditorProps>((props, ref) => (
|
||||
<DocumentReadOnlyEditor {...props} forwardedRef={ref as MutableRefObject<EditorReadOnlyRefApi | null>} />
|
||||
));
|
||||
|
||||
DocumentReadOnlyEditorWithRef.displayName = "DocumentReadOnlyEditorWithRef";
|
||||
|
||||
export { DocumentReadOnlyEditorWithRef };
|
||||
@@ -98,6 +98,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorHookProps) =>
|
||||
embedConfig: embedHandler,
|
||||
fileHandler,
|
||||
flaggedExtensions,
|
||||
isEditable: editable,
|
||||
provider,
|
||||
userDetails: user,
|
||||
}),
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export interface IMarking {
|
||||
type: "heading";
|
||||
level: number;
|
||||
text: string;
|
||||
sequence: number;
|
||||
}
|
||||
|
||||
export const useEditorMarkings = () => {
|
||||
const [markings, setMarkings] = useState<IMarking[]>([]);
|
||||
|
||||
const updateMarkings = useCallback((html: string) => {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, "text/html");
|
||||
const headings = doc.querySelectorAll("h1, h2, h3");
|
||||
const tempMarkings: IMarking[] = [];
|
||||
let h1Sequence: number = 0;
|
||||
let h2Sequence: number = 0;
|
||||
let h3Sequence: number = 0;
|
||||
|
||||
headings.forEach((heading) => {
|
||||
const level = parseInt(heading.tagName[1]); // Extract the number from h1, h2, h3
|
||||
tempMarkings.push({
|
||||
type: "heading",
|
||||
level: level,
|
||||
text: heading.textContent || "",
|
||||
sequence: level === 1 ? ++h1Sequence : level === 2 ? ++h2Sequence : ++h3Sequence,
|
||||
});
|
||||
});
|
||||
|
||||
setMarkings(tempMarkings);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
updateMarkings,
|
||||
markings,
|
||||
};
|
||||
};
|
||||
@@ -70,7 +70,7 @@ export const useEditor = (props: TEditorHookProps) => {
|
||||
}),
|
||||
...extensions,
|
||||
],
|
||||
content: typeof initialValue === "string" && initialValue.trim() !== "" ? initialValue : "<p></p>",
|
||||
content: initialValue,
|
||||
onCreate: () => handleEditorReady?.(true),
|
||||
onTransaction: () => {
|
||||
onTransaction?.();
|
||||
|
||||
@@ -46,3 +46,10 @@ export type TRealtimeConfig = {
|
||||
url: string;
|
||||
queryParams: TWebhookConnectionQueryParams;
|
||||
};
|
||||
|
||||
export type IMarking = {
|
||||
type: "heading";
|
||||
level: number;
|
||||
text: string;
|
||||
sequence: number;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Extensions, JSONContent } from "@tiptap/core";
|
||||
import { Selection } from "@tiptap/pm/state";
|
||||
import type { Content, Extensions, JSONContent } from "@tiptap/core";
|
||||
import type { Selection } from "@tiptap/pm/state";
|
||||
// extension types
|
||||
import type { TTextAlign } from "@/extensions";
|
||||
// helpers
|
||||
@@ -160,6 +160,14 @@ export interface ICollaborativeDocumentEditorProps
|
||||
user: TUserDetails;
|
||||
}
|
||||
|
||||
export interface IDocumentEditorProps extends Omit<IEditorProps, "initialValue" | "onEnterKeyPress" | "value"> {
|
||||
aiHandler?: TAIHandler;
|
||||
editable: boolean;
|
||||
embedHandler: TEmbedConfig;
|
||||
user?: TUserDetails;
|
||||
value: Content;
|
||||
}
|
||||
|
||||
// read only editor props
|
||||
export interface IReadOnlyEditorProps
|
||||
extends Pick<
|
||||
@@ -181,10 +189,6 @@ export interface IReadOnlyEditorProps
|
||||
|
||||
export type ILiteTextReadOnlyEditorProps = IReadOnlyEditorProps;
|
||||
|
||||
export interface IDocumentReadOnlyEditorProps extends IReadOnlyEditorProps {
|
||||
embedHandler: TEmbedConfig;
|
||||
}
|
||||
|
||||
export interface EditorEvents {
|
||||
beforeCreate: never;
|
||||
create: never;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { HocuspocusProvider } from "@hocuspocus/provider";
|
||||
import type { Content } from "@tiptap/core";
|
||||
import type { EditorProps } from "@tiptap/pm/view";
|
||||
// local imports
|
||||
import type { ICollaborativeDocumentEditorProps, IEditorProps, IReadOnlyEditorProps } from "./editor";
|
||||
@@ -27,7 +28,7 @@ export type TEditorHookProps = TCoreHookProps &
|
||||
> & {
|
||||
editable: boolean;
|
||||
enableHistory: boolean;
|
||||
initialValue?: string;
|
||||
initialValue?: Content;
|
||||
provider?: HocuspocusProvider;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,30 +9,18 @@ import "./styles/drag-drop.css";
|
||||
// editors
|
||||
export {
|
||||
CollaborativeDocumentEditorWithRef,
|
||||
DocumentReadOnlyEditorWithRef,
|
||||
DocumentEditorWithRef,
|
||||
LiteTextEditorWithRef,
|
||||
LiteTextReadOnlyEditorWithRef,
|
||||
RichTextEditorWithRef,
|
||||
} from "@/components/editors";
|
||||
|
||||
export { isCellSelection } from "@/extensions/table/table/utilities/helpers";
|
||||
|
||||
// constants
|
||||
export * from "@/constants/common";
|
||||
|
||||
// helpers
|
||||
export * from "@/helpers/common";
|
||||
export * from "@/helpers/editor-commands";
|
||||
export * from "@/helpers/yjs-utils";
|
||||
export * from "@/extensions/table/table";
|
||||
|
||||
// components
|
||||
export * from "@/components/menus";
|
||||
|
||||
// hooks
|
||||
export { useEditor } from "@/hooks/use-editor";
|
||||
export { type IMarking, useEditorMarkings } from "@/hooks/use-editor-markings";
|
||||
export { useReadOnlyEditor } from "@/hooks/use-read-only-editor";
|
||||
|
||||
export { CORE_EXTENSIONS } from "@/constants/extension";
|
||||
export { ADDITIONAL_EXTENSIONS } from "@/plane-editor/constants/extensions";
|
||||
|
||||
Reference in New Issue
Block a user