[WIKI-345] chore: pass disabled and flagged extensions to block menu (#7152)

* chore: refactor editor

* sync changes

* feat: api service update

* refactor : update sync

* fix : package sync

* fix: requested changes

* fix : embedhandler type

* fix : remove commands

* refactor : space

* refactor : rich lite editors

* refactor : minor ce changes

* chore : minor ui fix

* package: tldjs

* refactor : remove tldjs

* refactor: flagged

* refactor: flagged

* chore : remove disbaled check in menu

* refactor: fix space

* refactor: NodeViewProps

* refactor: type

* refactor : update community types

* refactor : remove external embed CE

* remove : external embed config from ce

* refactor : update disabled

* chore: pass disabled

* chore : update utils
This commit is contained in:
Vipin Chaudhary 2025-08-26 02:23:50 +05:30 committed by GitHub
parent 8801ab0081
commit 0fe7da6265
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 53 additions and 11 deletions

View File

@ -14,6 +14,7 @@ export abstract class APIService {
this.axiosInstance = axios.create({ this.axiosInstance = axios.create({
baseURL, baseURL,
withCredentials: true, withCredentials: true,
timeout: 20000,
}); });
} }

View File

@ -71,7 +71,7 @@ export const isCommentEmpty = (comment: string | undefined): boolean => {
return ( return (
comment?.trim() === "" || comment?.trim() === "" ||
comment === "<p></p>" || comment === "<p></p>" ||
isEmptyHtmlString(comment ?? "", ["img", "mention-component", "image-component"]) isEmptyHtmlString(comment ?? "", ["img", "mention-component", "image-component", "embed-component"])
); );
}; };

View File

@ -1,6 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
// plane imports // plane constants
import { EIssueCommentAccessSpecifier } from "@plane/constants"; import { EIssueCommentAccessSpecifier } from "@plane/constants";
// plane imports
import { type EditorRefApi, type ILiteTextEditorProps, LiteTextEditorWithRef, type TFileHandler } from "@plane/editor"; import { type EditorRefApi, type ILiteTextEditorProps, LiteTextEditorWithRef, type TFileHandler } from "@plane/editor";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import type { MakeOptional } from "@plane/types"; import type { MakeOptional } from "@plane/types";
@ -87,7 +88,6 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
// derived values // derived values
const isEmpty = isCommentEmpty(props.initialValue); const isEmpty = isCommentEmpty(props.initialValue);
const editorRef = isMutableRefObject<EditorRefApi>(ref) ? ref.current : null; const editorRef = isMutableRefObject<EditorRefApi>(ref) ? ref.current : null;
return ( return (
<div <div
className={cn( className={cn(

View File

@ -59,7 +59,6 @@ export const StickyEditor = React.forwardRef<EditorRefApi, StickyEditorWrapperPr
} }
// derived values // derived values
const editorRef = isMutableRefObject<EditorRefApi>(ref) ? ref.current : null; const editorRef = isMutableRefObject<EditorRefApi>(ref) ? ref.current : null;
return ( return (
<div <div
className={cn("relative border border-custom-border-200 rounded", parentClassName)} className={cn("relative border border-custom-border-200 rounded", parentClassName)}

View File

@ -12,7 +12,7 @@ const nextConfig = {
return [ return [
{ {
source: "/(.*)?", source: "/(.*)?",
headers: [{ key: "X-Frame-Options", value: "SAMEORIGIN" }], headers: [{ key: "X-Frame-Options", value: "DENY" }],
}, },
]; ];
}, },

View File

@ -108,6 +108,8 @@ const CollaborativeDocumentEditor: React.FC<ICollaborativeDocumentEditorProps> =
isTouchDevice={!!isTouchDevice} isTouchDevice={!!isTouchDevice}
isLoading={!hasServerSynced && !hasServerConnectionFailed} isLoading={!hasServerSynced && !hasServerConnectionFailed}
tabIndex={tabIndex} tabIndex={tabIndex}
flaggedExtensions={flaggedExtensions}
disabledExtensions={disabledExtensions}
/> />
); );
}; };

View File

@ -82,6 +82,7 @@ const DocumentEditor = (props: IDocumentEditorProps) => {
initialValue: value, initialValue: value,
mentionHandler, mentionHandler,
onChange, onChange,
embedHandler,
}); });
const editorContainerClassName = getEditorClassNames({ const editorContainerClassName = getEditorClassNames({
@ -98,6 +99,8 @@ const DocumentEditor = (props: IDocumentEditorProps) => {
editorContainerClassName={cn(editorContainerClassName, "document-editor")} editorContainerClassName={cn(editorContainerClassName, "document-editor")}
id={id} id={id}
isTouchDevice={!!isTouchDevice} isTouchDevice={!!isTouchDevice}
flaggedExtensions={flaggedExtensions}
disabledExtensions={disabledExtensions}
/> />
); );
}; };

View File

@ -5,7 +5,7 @@ import { cn } from "@plane/utils";
import { DocumentContentLoader, EditorContainer, EditorContentWrapper } from "@/components/editors"; import { DocumentContentLoader, EditorContainer, EditorContentWrapper } from "@/components/editors";
import { AIFeaturesMenu, BlockMenu, EditorBubbleMenu } from "@/components/menus"; import { AIFeaturesMenu, BlockMenu, EditorBubbleMenu } from "@/components/menus";
// types // types
import { TAIHandler, TDisplayConfig } from "@/types"; import { IEditorProps, TAIHandler, TDisplayConfig } from "@/types";
type Props = { type Props = {
aiHandler?: TAIHandler; aiHandler?: TAIHandler;
@ -18,6 +18,8 @@ type Props = {
isLoading?: boolean; isLoading?: boolean;
isTouchDevice: boolean; isTouchDevice: boolean;
tabIndex?: number; tabIndex?: number;
flaggedExtensions?: IEditorProps["flaggedExtensions"];
disabledExtensions?: IEditorProps["disabledExtensions"];
}; };
export const PageRenderer = (props: Props) => { export const PageRenderer = (props: Props) => {
@ -32,6 +34,8 @@ export const PageRenderer = (props: Props) => {
isLoading, isLoading,
isTouchDevice, isTouchDevice,
tabIndex, tabIndex,
flaggedExtensions,
disabledExtensions,
} = props; } = props;
return ( return (
@ -54,7 +58,11 @@ export const PageRenderer = (props: Props) => {
{editor.isEditable && !isTouchDevice && ( {editor.isEditable && !isTouchDevice && (
<div> <div>
{bubbleMenuEnabled && <EditorBubbleMenu editor={editor} />} {bubbleMenuEnabled && <EditorBubbleMenu editor={editor} />}
<BlockMenu editor={editor} /> <BlockMenu
editor={editor}
flaggedExtensions={flaggedExtensions}
disabledExtensions={disabledExtensions}
/>
<AIFeaturesMenu menu={aiHandler?.menu} /> <AIFeaturesMenu menu={aiHandler?.menu} />
</div> </div>
)} )}

View File

@ -41,6 +41,7 @@ export const EditorWrapper: React.FC<Props> = (props) => {
placeholder, placeholder,
tabIndex, tabIndex,
value, value,
embedHandler,
} = props; } = props;
const editor = useEditor({ const editor = useEditor({
@ -65,6 +66,7 @@ export const EditorWrapper: React.FC<Props> = (props) => {
placeholder, placeholder,
tabIndex, tabIndex,
value, value,
embedHandler,
}); });
const editorContainerClassName = getEditorClassNames({ const editorContainerClassName = getEditorClassNames({

View File

@ -1,8 +1,12 @@
import { autoUpdate, flip, hide, shift, useDismiss, useFloating, useInteractions } from "@floating-ui/react"; import { autoUpdate, flip, hide, shift, useDismiss, useFloating, useInteractions } from "@floating-ui/react";
import { Editor, useEditorState } from "@tiptap/react"; import { Editor, useEditorState } from "@tiptap/react";
import { FC, useCallback, useEffect, useRef, useState } from "react"; import { FC, useCallback, useEffect, useRef, useState } from "react";
// components // components
import { LinkView, LinkViewProps } from "@/components/links"; import { LinkView, LinkViewProps } from "@/components/links";
import { CORE_EXTENSIONS } from "@/constants/extension";
// components
import { getExtensionStorage } from "@/helpers/get-extension-storage";
type Props = { type Props = {
editor: Editor; editor: Editor;
@ -18,7 +22,7 @@ export const LinkViewContainer: FC<Props> = ({ editor, containerRef }) => {
const editorState = useEditorState({ const editorState = useEditorState({
editor, editor,
selector: ({ editor }: { editor: Editor }) => ({ selector: ({ editor }: { editor: Editor }) => ({
linkExtensionStorage: editor.storage.link, linkExtensionStorage: getExtensionStorage(editor, CORE_EXTENSIONS.CUSTOM_LINK),
}), }),
}); });

View File

@ -4,9 +4,12 @@ import { useCallback, useEffect, useRef } from "react";
import tippy, { Instance } from "tippy.js"; import tippy, { Instance } from "tippy.js";
// constants // constants
import { CORE_EXTENSIONS } from "@/constants/extension"; import { CORE_EXTENSIONS } from "@/constants/extension";
import { IEditorProps } from "@/types";
type Props = { type Props = {
editor: Editor; editor: Editor;
flaggedExtensions?: IEditorProps["flaggedExtensions"];
disabledExtensions?: IEditorProps["disabledExtensions"];
}; };
export const BlockMenu = (props: Props) => { export const BlockMenu = (props: Props) => {

View File

@ -81,6 +81,7 @@ declare module "@tiptap/core" {
export type CustomLinkStorage = { export type CustomLinkStorage = {
isPreviewOpen: boolean; isPreviewOpen: boolean;
posToInsert: { from: number; to: number }; posToInsert: { from: number; to: number };
isBubbleMenuOpen: boolean;
}; };
export const CustomLinkExtension = Mark.create<LinkOptions, CustomLinkStorage>({ export const CustomLinkExtension = Mark.create<LinkOptions, CustomLinkStorage>({

View File

@ -29,7 +29,7 @@ import {
// plane editor extensions // plane editor extensions
import { CoreEditorAdditionalExtensions } from "@/plane-editor/extensions"; import { CoreEditorAdditionalExtensions } from "@/plane-editor/extensions";
// types // types
import type { IEditorProps } from "@/types"; import type { IEditorProps, TEmbedConfig } from "@/types";
// local imports // local imports
import { CustomImageExtension } from "./custom-image/extension"; import { CustomImageExtension } from "./custom-image/extension";
import { EmojiExtension } from "./emoji/extension"; import { EmojiExtension } from "./emoji/extension";
@ -45,9 +45,11 @@ type TArguments = Pick<
| "mentionHandler" | "mentionHandler"
| "placeholder" | "placeholder"
| "tabIndex" | "tabIndex"
| "embedHandler"
> & { > & {
enableHistory: boolean; enableHistory: boolean;
editable: boolean; editable: boolean;
embedHandler?: TEmbedConfig;
}; };
export const CoreEditorExtensions = (args: TArguments): Extensions => { export const CoreEditorExtensions = (args: TArguments): Extensions => {
@ -60,6 +62,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
mentionHandler, mentionHandler,
placeholder, placeholder,
tabIndex, tabIndex,
embedHandler,
editable, editable,
} = args; } = args;
@ -115,6 +118,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
disabledExtensions, disabledExtensions,
flaggedExtensions, flaggedExtensions,
fileHandler, fileHandler,
embedHandler,
}), }),
]; ];

View File

@ -80,6 +80,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorHookProps) =>
); );
const editor = useEditor({ const editor = useEditor({
embedHandler,
disabledExtensions, disabledExtensions,
id, id,
editable, editable,

View File

@ -32,6 +32,7 @@ export const useEditor = (props: TEditorHookProps) => {
onAssetChange, onAssetChange,
onChange, onChange,
onEditorFocus, onEditorFocus,
embedHandler,
onTransaction, onTransaction,
placeholder, placeholder,
provider, provider,
@ -63,6 +64,7 @@ export const useEditor = (props: TEditorHookProps) => {
mentionHandler, mentionHandler,
placeholder, placeholder,
tabIndex, tabIndex,
embedHandler,
}), }),
...extensions, ...extensions,
], ],

View File

@ -21,6 +21,7 @@ const generalSelectors = [
".image-component", ".image-component",
".image-upload-component", ".image-upload-component",
".editor-callout-component", ".editor-callout-component",
".editor-embed-component",
].join(", "); ].join(", ");
const maxScrollSpeed = 20; const maxScrollSpeed = 20;
@ -103,6 +104,11 @@ export const nodeDOMAtCoords = (coords: { x: number; y: number }) => {
continue; continue;
} }
// Skip elements inside .editor-embed-component
if (elem.closest(".editor-embed-component") && !elem.matches(".editor-embed-component")) {
continue;
}
// apply general selector // apply general selector
if (elem.matches(generalSelectors)) { if (elem.matches(generalSelectors)) {
return elem; return elem;

View File

@ -2,6 +2,7 @@ import type { Content, Extensions, JSONContent, RawCommands } from "@tiptap/core
import type { MarkType, NodeType } from "@tiptap/pm/model"; import type { MarkType, NodeType } from "@tiptap/pm/model";
import type { Selection } from "@tiptap/pm/state"; import type { Selection } from "@tiptap/pm/state";
import type { EditorProps, EditorView } from "@tiptap/pm/view"; import type { EditorProps, EditorView } from "@tiptap/pm/view";
import type { NodeViewProps as TNodeViewProps } from "@tiptap/react";
// extension types // extension types
import type { TTextAlign } from "@/extensions"; import type { TTextAlign } from "@/extensions";
// types // types
@ -48,7 +49,8 @@ export type TEditorCommands =
| "text-align" | "text-align"
| "callout" | "callout"
| "attachment" | "attachment"
| "emoji"; | "emoji"
| "external-embed";
export type TCommandExtraProps = { export type TCommandExtraProps = {
image: { image: {
@ -138,6 +140,7 @@ export type IEditorProps = {
editorClassName?: string; editorClassName?: string;
editorProps?: EditorProps; editorProps?: EditorProps;
extensions?: Extensions; extensions?: Extensions;
embedHandler?: TEmbedConfig;
flaggedExtensions: TExtensions[]; flaggedExtensions: TExtensions[];
fileHandler: TFileHandler; fileHandler: TFileHandler;
forwardedRef?: React.MutableRefObject<EditorRefApi | null>; forwardedRef?: React.MutableRefObject<EditorRefApi | null>;
@ -191,3 +194,5 @@ export type EditorEvents = {
destroy: never; destroy: never;
ready: { height: number }; ready: { height: number };
}; };
export type NodeViewProps = TNodeViewProps;

View File

@ -13,6 +13,7 @@ type TCoreHookProps = Pick<
| "handleEditorReady" | "handleEditorReady"
| "isTouchDevice" | "isTouchDevice"
| "onEditorFocus" | "onEditorFocus"
| "embedHandler"
>; >;
export type TEditorHookProps = TCoreHookProps & export type TEditorHookProps = TCoreHookProps &

View File

@ -174,7 +174,7 @@ export const isCommentEmpty = (comment: string | undefined): boolean => {
return ( return (
comment?.trim() === "" || comment?.trim() === "" ||
comment === "<p></p>" || comment === "<p></p>" ||
isEmptyHtmlString(comment ?? "", ["img", "mention-component", "image-component"]) isEmptyHtmlString(comment ?? "", ["img", "mention-component", "image-component", "embed-component"])
); );
}; };