Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions packages/editor/src/ce/extensions/document-extensions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,31 @@ import { HocuspocusProvider } from "@hocuspocus/provider";
import { AnyExtension } from "@tiptap/core";
import { SlashCommands } from "@/extensions";
// plane editor types
import { TIssueEmbedConfig } from "@/plane-editor/types";
import { TEmbedConfig } from "@/plane-editor/types";
// types
import { TExtensions, TUserDetails } from "@/types";
import { TExtensions, TFileHandler, TUserDetails } from "@/types";

type Props = {
disabledExtensions?: TExtensions[];
issueEmbedConfig: TIssueEmbedConfig | undefined;
provider: HocuspocusProvider;
export type TDocumentEditorAdditionalExtensionsProps = {
disabledExtensions: TExtensions[];
embedConfig: TEmbedConfig | undefined;
fileHandler: TFileHandler;
provider?: HocuspocusProvider;
userDetails: TUserDetails;
};

type ExtensionConfig = {
export type TDocumentEditorAdditionalExtensionsRegistry = {
isEnabled: (disabledExtensions: TExtensions[]) => boolean;
getExtension: (props: Props) => AnyExtension;
getExtension: (props: TDocumentEditorAdditionalExtensionsProps) => AnyExtension;
};

const extensionRegistry: ExtensionConfig[] = [
const extensionRegistry: TDocumentEditorAdditionalExtensionsRegistry[] = [
{
isEnabled: (disabledExtensions) => !disabledExtensions.includes("slash-commands"),
getExtension: () => SlashCommands({}),
getExtension: ({ disabledExtensions }) => SlashCommands({ disabledExtensions }),
},
];

export const DocumentEditorAdditionalExtensions = (_props: Props) => {
export const DocumentEditorAdditionalExtensions = (_props: TDocumentEditorAdditionalExtensionsProps) => {
const { disabledExtensions = [] } = _props;

const documentExtensions = extensionRegistry
Expand Down
41 changes: 41 additions & 0 deletions packages/editor/src/ce/extensions/rich-text/extensions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { AnyExtension, Extensions } from "@tiptap/core";
// extensions
import { SlashCommands } from "@/extensions/slash-commands/root";
// types
import { TExtensions, TFileHandler } from "@/types";

export type TRichTextEditorAdditionalExtensionsProps = {
disabledExtensions: TExtensions[];
fileHandler: TFileHandler;
};

/**
* Registry entry configuration for extensions
*/
export type TRichTextEditorAdditionalExtensionsRegistry = {
/** Determines if the extension should be enabled based on disabled extensions */
isEnabled: (disabledExtensions: TExtensions[]) => boolean;
/** Returns the extension instance(s) when enabled */
getExtension: (props: TRichTextEditorAdditionalExtensionsProps) => AnyExtension | undefined;
};

const extensionRegistry: TRichTextEditorAdditionalExtensionsRegistry[] = [
{
isEnabled: (disabledExtensions) => !disabledExtensions.includes("slash-commands"),
getExtension: ({ disabledExtensions }) =>
SlashCommands({
disabledExtensions,
}),
},
];

export const RichTextEditorAdditionalExtensions = (props: TRichTextEditorAdditionalExtensionsProps) => {
const { disabledExtensions } = props;

const extensions: Extensions = extensionRegistry
.filter((config) => config.isEnabled(disabledExtensions))
.map((config) => config.getExtension(props))
.filter((extension): extension is AnyExtension => extension !== undefined);

return extensions;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AnyExtension, Extensions } from "@tiptap/core";
// types
import { TExtensions, TReadOnlyFileHandler } from "@/types";

export type TRichTextReadOnlyEditorAdditionalExtensionsProps = {
disabledExtensions: TExtensions[];
fileHandler: TReadOnlyFileHandler;
};

/**
* Registry entry configuration for extensions
*/
export type TRichTextReadOnlyEditorAdditionalExtensionsRegistry = {
/** Determines if the extension should be enabled based on disabled extensions */
isEnabled: (disabledExtensions: TExtensions[]) => boolean;
/** Returns the extension instance(s) when enabled */
getExtension: (props: TRichTextReadOnlyEditorAdditionalExtensionsProps) => AnyExtension | undefined;
};

const extensionRegistry: TRichTextReadOnlyEditorAdditionalExtensionsRegistry[] = [];

export const RichTextReadOnlyEditorAdditionalExtensions = (props: TRichTextReadOnlyEditorAdditionalExtensionsProps) => {
const { disabledExtensions } = props;

const extensions: Extensions = extensionRegistry
.filter((config) => config.isEnabled(disabledExtensions))
.map((config) => config.getExtension(props))
.filter((extension): extension is AnyExtension => extension !== undefined);

return extensions;
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => {
disabledExtensions,
displayConfig = DEFAULT_DISPLAY_CONFIG,
editorClassName = "",
extensions,
fileHandler,
forwardedRef,
id,
Expand All @@ -25,6 +26,7 @@ export const ReadOnlyEditorWrapper = (props: IReadOnlyEditorProps) => {
const editor = useReadOnlyEditor({
disabledExtensions,
editorClassName,
extensions,
fileHandler,
forwardedRef,
initialValue,
Expand Down
25 changes: 15 additions & 10 deletions packages/editor/src/core/components/editors/rich-text/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import { forwardRef, useCallback } from "react";
import { EditorWrapper } from "@/components/editors";
import { EditorBubbleMenu } from "@/components/menus";
// extensions
import { SideMenuExtension, SlashCommands } from "@/extensions";
import { SideMenuExtension } from "@/extensions";
// plane editor imports
import { RichTextEditorAdditionalExtensions } from "@/plane-editor/extensions/rich-text/extensions";
// types
import { EditorRefApi, IRichTextEditor } from "@/types";

const RichTextEditor = (props: IRichTextEditor) => {
const { disabledExtensions, dragDropEnabled, bubbleMenuEnabled = true, extensions: externalExtensions = [] } = props;
const {
disabledExtensions,
dragDropEnabled,
fileHandler,
bubbleMenuEnabled = true,
extensions: externalExtensions = [],
} = props;

const getExtensions = useCallback(() => {
const extensions = [
Expand All @@ -17,17 +25,14 @@ const RichTextEditor = (props: IRichTextEditor) => {
aiEnabled: false,
dragDropEnabled: !!dragDropEnabled,
}),
...RichTextEditorAdditionalExtensions({
disabledExtensions,
fileHandler,
}),
];
if (!disabledExtensions?.includes("slash-commands")) {
extensions.push(
SlashCommands({
disabledExtensions,
})
);
}

return extensions;
}, [dragDropEnabled, disabledExtensions, externalExtensions]);
}, [dragDropEnabled, disabledExtensions, externalExtensions, fileHandler]);

return (
<EditorWrapper {...props} extensions={getExtensions()}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
import { forwardRef } from "react";
import { forwardRef, useCallback } from "react";
// plane editor extensions
import { RichTextReadOnlyEditorAdditionalExtensions } from "@/plane-editor/extensions/rich-text/read-only-extensions";
// types
import { EditorReadOnlyRefApi, IRichTextReadOnlyEditor } from "@/types";
// local imports
import { ReadOnlyEditorWrapper } from "../read-only-editor-wrapper";

const RichTextReadOnlyEditorWithRef = forwardRef<EditorReadOnlyRefApi, IRichTextReadOnlyEditor>((props, ref) => (
<ReadOnlyEditorWrapper {...props} forwardedRef={ref as React.MutableRefObject<EditorReadOnlyRefApi | null>} />
));
const RichTextReadOnlyEditorWithRef = forwardRef<EditorReadOnlyRefApi, IRichTextReadOnlyEditor>((props, ref) => {
const { disabledExtensions, fileHandler } = props;

const getExtensions = useCallback(() => {
const extensions = [
...RichTextReadOnlyEditorAdditionalExtensions({
disabledExtensions,
fileHandler,
}),
];

return extensions;
}, [disabledExtensions, fileHandler]);

return (
<ReadOnlyEditorWrapper
{...props}
extensions={getExtensions()}
forwardedRef={ref as React.MutableRefObject<EditorReadOnlyRefApi | null>}
/>
);
});

RichTextReadOnlyEditorWithRef.displayName = "RichReadOnlyEditorWithRef";

Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/core/extensions/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,9 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
CustomTextAlignExtension,
CustomCalloutExtension,
UtilityExtension({
isEditable: editable,
disabledExtensions,
fileHandler,
isEditable: editable,
}),
CustomColorExtension,
...CoreEditorAdditionalExtensions({
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/core/extensions/read-only-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
CustomTextAlignExtension,
CustomCalloutReadOnlyExtension,
UtilityExtension({
isEditable: false,
disabledExtensions,
fileHandler,
isEditable: false,
}),
...CoreReadOnlyEditorAdditionalExtensions({
disabledExtensions,
Expand Down
14 changes: 9 additions & 5 deletions packages/editor/src/core/extensions/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DropHandlerPlugin } from "@/plugins/drop";
import { FilePlugins } from "@/plugins/file/root";
import { MarkdownClipboardPlugin } from "@/plugins/markdown-clipboard";
// types
import { TFileHandler, TReadOnlyFileHandler } from "@/types";
import { TExtensions, TFileHandler, TReadOnlyFileHandler } from "@/types";

declare module "@tiptap/core" {
interface Commands {
Expand All @@ -24,13 +24,14 @@ export interface UtilityExtensionStorage {
}

type Props = {
disabledExtensions: TExtensions[];
fileHandler: TFileHandler | TReadOnlyFileHandler;
isEditable: boolean;
};

export const UtilityExtension = (props: Props) => {
const { fileHandler, isEditable } = props;
const { restore: restoreImageFn } = fileHandler;
const { disabledExtensions, fileHandler, isEditable } = props;
const { restore } = fileHandler;

return Extension.create<Record<string, unknown>, UtilityExtensionStorage>({
name: "utility",
Expand All @@ -45,12 +46,15 @@ export const UtilityExtension = (props: Props) => {
}),
...codemark({ markType: this.editor.schema.marks.code }),
MarkdownClipboardPlugin(this.editor),
DropHandlerPlugin(this.editor),
DropHandlerPlugin({
disabledExtensions,
editor: this.editor,
}),
];
},

onCreate() {
restorePublicImages(this.editor, restoreImageFn);
restorePublicImages(this.editor, restore);
},

addStorage() {
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/core/hooks/use-collaborative-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
...(extensions ?? []),
...DocumentEditorAdditionalExtensions({
disabledExtensions,
issueEmbedConfig: embedHandler?.issue,
embedConfig: embedHandler,
fileHandler,
provider,
userDetails: user,
}),
Expand Down
21 changes: 16 additions & 5 deletions packages/editor/src/core/plugins/drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ import { Plugin, PluginKey } from "@tiptap/pm/state";
// constants
import { ACCEPTED_ATTACHMENT_MIME_TYPES, ACCEPTED_IMAGE_MIME_TYPES } from "@/constants/config";
// types
import { TEditorCommands } from "@/types";
import { TEditorCommands, TExtensions } from "@/types";

export const DropHandlerPlugin = (editor: Editor): Plugin =>
new Plugin({
type Props = {
disabledExtensions: TExtensions[];
editor: Editor;
};

export const DropHandlerPlugin = (props: Props): Plugin => {
const { disabledExtensions, editor } = props;

return new Plugin({
key: new PluginKey("drop-handler-plugin"),
props: {
handlePaste: (view, event) => {
Expand All @@ -25,6 +32,7 @@ export const DropHandlerPlugin = (editor: Editor): Plugin =>
if (acceptedFiles.length) {
const pos = view.state.selection.from;
insertFilesSafely({
disabledExtensions,
editor,
files: acceptedFiles,
initialPos: pos,
Expand Down Expand Up @@ -58,6 +66,7 @@ export const DropHandlerPlugin = (editor: Editor): Plugin =>
if (coordinates) {
const pos = coordinates.pos;
insertFilesSafely({
disabledExtensions,
editor,
files: acceptedFiles,
initialPos: pos,
Expand All @@ -71,8 +80,10 @@ export const DropHandlerPlugin = (editor: Editor): Plugin =>
},
},
});
};

type InsertFilesSafelyArgs = {
disabledExtensions: TExtensions[];
editor: Editor;
event: "insert" | "drop";
files: File[];
Expand All @@ -81,7 +92,7 @@ type InsertFilesSafelyArgs = {
};

export const insertFilesSafely = async (args: InsertFilesSafelyArgs) => {
const { editor, event, files, initialPos, type } = args;
const { disabledExtensions, editor, event, files, initialPos, type } = args;
let pos = initialPos;

for (const file of files) {
Expand All @@ -100,7 +111,7 @@ export const insertFilesSafely = async (args: InsertFilesSafelyArgs) => {
else if (ACCEPTED_ATTACHMENT_MIME_TYPES.includes(file.type)) fileType = "attachment";
}
// insert file depending on the type at the current position
if (fileType === "image") {
if (fileType === "image" && !disabledExtensions.includes("image")) {
editor.commands.insertImageComponent({
file,
pos,
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/core/types/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export interface IReadOnlyEditorProps {
disabledExtensions: TExtensions[];
displayConfig?: TDisplayConfig;
editorClassName?: string;
extensions?: Extensions;
fileHandler: TReadOnlyFileHandler;
forwardedRef?: React.MutableRefObject<EditorReadOnlyRefApi | null>;
id: string;
Expand Down