From 234513278f6c1467d58f4d5404ffc003c99b31b1 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 26 Nov 2024 18:08:32 +0530 Subject: [PATCH] fix: refactor editor extensions code spliting --- .../src/ce/extensions/core/extensions.ts | 3 + .../editor/src/ce/extensions/core/index.ts | 2 + .../extensions/core/read-only-extensions.ts | 3 + .../src/ce/extensions/core/without-props.ts | 3 + packages/editor/src/ce/extensions/index.ts | 1 + packages/editor/src/ce/types/editor.ts | 1 + packages/editor/src/ce/types/index.ts | 1 + .../src/core/extensions/callout/block.tsx | 56 ----------- .../extensions/callout/color-selector.tsx | 75 -------------- .../extensions/callout/extension-config.ts | 72 -------------- .../src/core/extensions/callout/extension.tsx | 68 ------------- .../src/core/extensions/callout/index.ts | 3 - .../core/extensions/callout/logo-selector.tsx | 97 ------------------- .../callout/read-only-extension.tsx | 14 --- .../src/core/extensions/callout/types.ts | 26 ----- .../src/core/extensions/callout/utils.ts | 85 ---------------- .../src/core/extensions/core-without-props.ts | 7 +- .../editor/src/core/extensions/extensions.tsx | 8 +- packages/editor/src/core/extensions/index.ts | 1 - .../core/extensions/read-only-extensions.tsx | 8 +- .../slash-commands/command-items-list.tsx | 36 +++---- .../core/extensions/slash-commands/root.tsx | 9 +- .../src/core/helpers/editor-commands.ts | 4 - packages/editor/src/core/types/editor.ts | 6 +- .../core/types/slash-commands-suggestion.ts | 2 + 25 files changed, 59 insertions(+), 532 deletions(-) create mode 100644 packages/editor/src/ce/extensions/core/extensions.ts create mode 100644 packages/editor/src/ce/extensions/core/index.ts create mode 100644 packages/editor/src/ce/extensions/core/read-only-extensions.ts create mode 100644 packages/editor/src/ce/extensions/core/without-props.ts create mode 100644 packages/editor/src/ce/types/editor.ts delete mode 100644 packages/editor/src/core/extensions/callout/block.tsx delete mode 100644 packages/editor/src/core/extensions/callout/color-selector.tsx delete mode 100644 packages/editor/src/core/extensions/callout/extension-config.ts delete mode 100644 packages/editor/src/core/extensions/callout/extension.tsx delete mode 100644 packages/editor/src/core/extensions/callout/index.ts delete mode 100644 packages/editor/src/core/extensions/callout/logo-selector.tsx delete mode 100644 packages/editor/src/core/extensions/callout/read-only-extension.tsx delete mode 100644 packages/editor/src/core/extensions/callout/types.ts delete mode 100644 packages/editor/src/core/extensions/callout/utils.ts diff --git a/packages/editor/src/ce/extensions/core/extensions.ts b/packages/editor/src/ce/extensions/core/extensions.ts new file mode 100644 index 000000000..8f4fd4185 --- /dev/null +++ b/packages/editor/src/ce/extensions/core/extensions.ts @@ -0,0 +1,3 @@ +import { Extensions } from "@tiptap/core"; + +export const CoreEditorAdditionalExtensions = (): Extensions => []; diff --git a/packages/editor/src/ce/extensions/core/index.ts b/packages/editor/src/ce/extensions/core/index.ts new file mode 100644 index 000000000..9ffc978c3 --- /dev/null +++ b/packages/editor/src/ce/extensions/core/index.ts @@ -0,0 +1,2 @@ +export * from "./extensions"; +export * from "./read-only-extensions"; diff --git a/packages/editor/src/ce/extensions/core/read-only-extensions.ts b/packages/editor/src/ce/extensions/core/read-only-extensions.ts new file mode 100644 index 000000000..b7789af73 --- /dev/null +++ b/packages/editor/src/ce/extensions/core/read-only-extensions.ts @@ -0,0 +1,3 @@ +import { Extensions } from "@tiptap/core"; + +export const CoreReadOnlyEditorAdditionalExtensions = (): Extensions => []; diff --git a/packages/editor/src/ce/extensions/core/without-props.ts b/packages/editor/src/ce/extensions/core/without-props.ts new file mode 100644 index 000000000..0debff0ea --- /dev/null +++ b/packages/editor/src/ce/extensions/core/without-props.ts @@ -0,0 +1,3 @@ +import { Extensions } from "@tiptap/core"; + +export const CoreEditorAdditionalExtensionsWithoutProps: Extensions = []; diff --git a/packages/editor/src/ce/extensions/index.ts b/packages/editor/src/ce/extensions/index.ts index 4a975b8c5..b25cbcfc7 100644 --- a/packages/editor/src/ce/extensions/index.ts +++ b/packages/editor/src/ce/extensions/index.ts @@ -1 +1,2 @@ +export * from "./core"; export * from "./document-extensions"; diff --git a/packages/editor/src/ce/types/editor.ts b/packages/editor/src/ce/types/editor.ts new file mode 100644 index 000000000..52ef2f743 --- /dev/null +++ b/packages/editor/src/ce/types/editor.ts @@ -0,0 +1 @@ +export type TEditorAdditionalCommands = never; diff --git a/packages/editor/src/ce/types/index.ts b/packages/editor/src/ce/types/index.ts index f30596cb0..8b6f01847 100644 --- a/packages/editor/src/ce/types/index.ts +++ b/packages/editor/src/ce/types/index.ts @@ -1 +1,2 @@ +export * from "./editor"; export * from "./issue-embed"; diff --git a/packages/editor/src/core/extensions/callout/block.tsx b/packages/editor/src/core/extensions/callout/block.tsx deleted file mode 100644 index b6c6d7991..000000000 --- a/packages/editor/src/core/extensions/callout/block.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useState } from "react"; -import { NodeViewContent, NodeViewProps, NodeViewWrapper } from "@tiptap/react"; -// constants -import { COLORS_LIST } from "@/constants/common"; -// local components -import { CalloutBlockColorSelector } from "./color-selector"; -import { CalloutBlockLogoSelector } from "./logo-selector"; -// types -import { EAttributeNames, TCalloutBlockAttributes } from "./types"; -// utils -import { updateStoredBackgroundColor } from "./utils"; - -type Props = NodeViewProps & { - node: NodeViewProps["node"] & { - attrs: TCalloutBlockAttributes; - }; - updateAttributes: (attrs: Partial) => void; -}; - -export const CustomCalloutBlock: React.FC = (props) => { - const { editor, node, updateAttributes } = props; - // states - const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false); - const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); - // derived values - const activeBackgroundColor = COLORS_LIST.find((c) => node.attrs["data-background"] === c.key)?.backgroundColor; - - return ( - - setIsEmojiPickerOpen(val)} - updateAttributes={updateAttributes} - /> - setIsColorPickerOpen((prev) => !prev)} - onSelect={(val) => { - updateAttributes({ - [EAttributeNames.BACKGROUND]: val, - }); - updateStoredBackgroundColor(val); - }} - /> - - - ); -}; diff --git a/packages/editor/src/core/extensions/callout/color-selector.tsx b/packages/editor/src/core/extensions/callout/color-selector.tsx deleted file mode 100644 index 489b05166..000000000 --- a/packages/editor/src/core/extensions/callout/color-selector.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Ban, ChevronDown } from "lucide-react"; -// constants -import { COLORS_LIST } from "@/constants/common"; -// helpers -import { cn } from "@/helpers/common"; - -type Props = { - disabled: boolean; - isOpen: boolean; - onSelect: (color: string | null) => void; - toggleDropdown: () => void; -}; - -export const CalloutBlockColorSelector: React.FC = (props) => { - const { disabled, isOpen, onSelect, toggleDropdown } = props; - - const handleColorSelect = (val: string | null) => { - onSelect(val); - toggleDropdown(); - }; - - return ( -
-
- - {isOpen && ( -
-
- {COLORS_LIST.map((color) => ( - -
-
- )} -
-
- ); -}; diff --git a/packages/editor/src/core/extensions/callout/extension-config.ts b/packages/editor/src/core/extensions/callout/extension-config.ts deleted file mode 100644 index 546311509..000000000 --- a/packages/editor/src/core/extensions/callout/extension-config.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Node, mergeAttributes } from "@tiptap/core"; -import { Node as NodeType } from "@tiptap/pm/model"; -import { MarkdownSerializerState } from "@tiptap/pm/markdown"; -// types -import { EAttributeNames, TCalloutBlockAttributes } from "./types"; -// utils -import { DEFAULT_CALLOUT_BLOCK_ATTRIBUTES } from "./utils"; - -// Extend Tiptap's Commands interface -declare module "@tiptap/core" { - interface Commands { - calloutComponent: { - insertCallout: () => ReturnType; - }; - } -} - -export const CustomCalloutExtensionConfig = Node.create({ - name: "calloutComponent", - group: "block", - content: "block+", - - addAttributes() { - const attributes = { - // Reduce instead of map to accumulate the attributes directly into an object - ...Object.values(EAttributeNames).reduce((acc, value) => { - acc[value] = { - default: DEFAULT_CALLOUT_BLOCK_ATTRIBUTES[value], - }; - return acc; - }, {}), - }; - return attributes; - }, - - addStorage() { - return { - markdown: { - serialize(state: MarkdownSerializerState, node: NodeType) { - const attrs = node.attrs as TCalloutBlockAttributes; - const logoInUse = attrs["data-logo-in-use"]; - // add callout logo - if (logoInUse === "emoji") { - state.write( - `> ${attrs[\n` - ); - } else { - state.write(`> ${attrs["data-icon-name"]} icon\n`); - } - // add an empty line after the logo - state.write("> \n"); - // add '> ' before each line of the callout content - state.wrapBlock("> ", null, node, () => state.renderContent(node)); - state.closeBlock(node); - }, - }, - }; - }, - - parseHTML() { - return [ - { - tag: `div[${EAttributeNames.BLOCK_TYPE}="${DEFAULT_CALLOUT_BLOCK_ATTRIBUTES[EAttributeNames.BLOCK_TYPE]}"]`, - }, - ]; - }, - - // Render HTML for the callout node - renderHTML({ HTMLAttributes }) { - return ["div", mergeAttributes(HTMLAttributes), 0]; - }, -}); diff --git a/packages/editor/src/core/extensions/callout/extension.tsx b/packages/editor/src/core/extensions/callout/extension.tsx deleted file mode 100644 index a83964b37..000000000 --- a/packages/editor/src/core/extensions/callout/extension.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { findParentNodeClosestToPos, Predicate, ReactNodeViewRenderer } from "@tiptap/react"; -// extensions -import { CustomCalloutBlock } from "@/extensions"; -// helpers -import { insertEmptyParagraphAtNodeBoundaries } from "@/helpers/insert-empty-paragraph-at-node-boundary"; -// config -import { CustomCalloutExtensionConfig } from "./extension-config"; -// utils -import { getStoredBackgroundColor, getStoredLogo } from "./utils"; - -export const CustomCalloutExtension = CustomCalloutExtensionConfig.extend({ - selectable: true, - draggable: true, - - addCommands() { - return { - insertCallout: - () => - ({ commands }) => { - // get stored logo values and background color from the local storage - const storedLogoValues = getStoredLogo(); - const storedBackgroundValue = getStoredBackgroundColor(); - - return commands.insertContent({ - type: this.name, - content: [ - { - type: "paragraph", - }, - ], - attrs: { - ...storedLogoValues, - "data-background": storedBackgroundValue, - }, - }); - }, - }; - }, - - addKeyboardShortcuts() { - return { - Backspace: ({ editor }) => { - const { $from, empty } = editor.state.selection; - try { - const isParentNodeCallout: Predicate = (node) => node.type === this.type; - const parentNodeDetails = findParentNodeClosestToPos($from, isParentNodeCallout); - // Check if selection is empty and at the beginning of the callout - if (empty && parentNodeDetails) { - const isCursorAtCalloutBeginning = $from.pos === parentNodeDetails.start + 1; - if (parentNodeDetails.node.content.size > 2 && isCursorAtCalloutBeginning) { - editor.commands.setTextSelection(parentNodeDetails.pos - 1); - return true; - } - } - } catch (error) { - console.error("Error in performing backspace action on callout", error); - } - return false; // Allow the default behavior if conditions are not met - }, - ArrowDown: insertEmptyParagraphAtNodeBoundaries("down", this.name), - ArrowUp: insertEmptyParagraphAtNodeBoundaries("up", this.name), - }; - }, - - addNodeView() { - return ReactNodeViewRenderer(CustomCalloutBlock); - }, -}); diff --git a/packages/editor/src/core/extensions/callout/index.ts b/packages/editor/src/core/extensions/callout/index.ts deleted file mode 100644 index 57915d313..000000000 --- a/packages/editor/src/core/extensions/callout/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./block"; -export * from "./extension"; -export * from "./read-only-extension"; diff --git a/packages/editor/src/core/extensions/callout/logo-selector.tsx b/packages/editor/src/core/extensions/callout/logo-selector.tsx deleted file mode 100644 index 4e9f966af..000000000 --- a/packages/editor/src/core/extensions/callout/logo-selector.tsx +++ /dev/null @@ -1,97 +0,0 @@ -// plane helpers -import { convertHexEmojiToDecimal } from "@plane/helpers"; -// plane ui -import { EmojiIconPicker, EmojiIconPickerTypes, Logo, TEmojiLogoProps } from "@plane/ui"; -// helpers -import { cn } from "@/helpers/common"; -// types -import { TCalloutBlockAttributes } from "./types"; -// utils -import { DEFAULT_CALLOUT_BLOCK_ATTRIBUTES, updateStoredLogo } from "./utils"; - -type Props = { - blockAttributes: TCalloutBlockAttributes; - disabled: boolean; - handleOpen: (val: boolean) => void; - isOpen: boolean; - updateAttributes: (attrs: Partial) => void; -}; - -export const CalloutBlockLogoSelector: React.FC = (props) => { - const { blockAttributes, disabled, handleOpen, isOpen, updateAttributes } = props; - - const logoValue: TEmojiLogoProps = { - in_use: blockAttributes["data-logo-in-use"], - icon: { - color: blockAttributes["data-icon-color"], - name: blockAttributes["data-icon-name"], - }, - emoji: { - value: blockAttributes["data-emoji-unicode"]?.toString(), - url: blockAttributes["data-emoji-url"], - }, - }; - - return ( -
- } - onChange={(val) => { - // construct the new logo value based on the type of value - let newLogoValue: Partial = {}; - let newLogoValueToStoreInLocalStorage: TEmojiLogoProps = { - in_use: "emoji", - emoji: { - value: DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-emoji-unicode"], - url: DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-emoji-url"], - }, - }; - if (val.type === "emoji") { - newLogoValue = { - "data-emoji-unicode": convertHexEmojiToDecimal(val.value.unified), - "data-emoji-url": val.value.imageUrl, - }; - newLogoValueToStoreInLocalStorage = { - in_use: "emoji", - emoji: { - value: convertHexEmojiToDecimal(val.value.unified), - url: val.value.imageUrl, - }, - }; - } else if (val.type === "icon") { - newLogoValue = { - "data-icon-name": val.value.name, - "data-icon-color": val.value.color, - }; - newLogoValueToStoreInLocalStorage = { - in_use: "icon", - icon: { - name: val.value.name, - color: val.value.color, - }, - }; - } - // update node attributes - updateAttributes({ - "data-logo-in-use": val.type, - ...newLogoValue, - }); - // update stored logo in local storage - updateStoredLogo(newLogoValueToStoreInLocalStorage); - handleOpen(false); - }} - defaultIconColor={logoValue?.in_use && logoValue.in_use === "icon" ? logoValue?.icon?.color : undefined} - defaultOpen={logoValue.in_use === "emoji" ? EmojiIconPickerTypes.EMOJI : EmojiIconPickerTypes.ICON} - disabled={disabled} - searchDisabled - /> -
- ); -}; diff --git a/packages/editor/src/core/extensions/callout/read-only-extension.tsx b/packages/editor/src/core/extensions/callout/read-only-extension.tsx deleted file mode 100644 index ad7dbd50d..000000000 --- a/packages/editor/src/core/extensions/callout/read-only-extension.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { ReactNodeViewRenderer } from "@tiptap/react"; -// extensions -import { CustomCalloutBlock } from "@/extensions"; -// config -import { CustomCalloutExtensionConfig } from "./extension-config"; - -export const CustomCalloutReadOnlyExtension = CustomCalloutExtensionConfig.extend({ - selectable: false, - draggable: false, - - addNodeView() { - return ReactNodeViewRenderer(CustomCalloutBlock); - }, -}); diff --git a/packages/editor/src/core/extensions/callout/types.ts b/packages/editor/src/core/extensions/callout/types.ts deleted file mode 100644 index 17c55d9e5..000000000 --- a/packages/editor/src/core/extensions/callout/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -export enum EAttributeNames { - ICON_COLOR = "data-icon-color", - ICON_NAME = "data-icon-name", - EMOJI_UNICODE = "data-emoji-unicode", - EMOJI_URL = "data-emoji-url", - LOGO_IN_USE = "data-logo-in-use", - BACKGROUND = "data-background", - BLOCK_TYPE = "data-block-type", -} - -export type TCalloutBlockIconAttributes = { - [EAttributeNames.ICON_COLOR]: string | undefined; - [EAttributeNames.ICON_NAME]: string | undefined; -}; - -export type TCalloutBlockEmojiAttributes = { - [EAttributeNames.EMOJI_UNICODE]: string | undefined; - [EAttributeNames.EMOJI_URL]: string | undefined; -}; - -export type TCalloutBlockAttributes = { - [EAttributeNames.LOGO_IN_USE]: "emoji" | "icon"; - [EAttributeNames.BACKGROUND]: string; - [EAttributeNames.BLOCK_TYPE]: "callout-component"; -} & TCalloutBlockIconAttributes & - TCalloutBlockEmojiAttributes; diff --git a/packages/editor/src/core/extensions/callout/utils.ts b/packages/editor/src/core/extensions/callout/utils.ts deleted file mode 100644 index c450cbdd2..000000000 --- a/packages/editor/src/core/extensions/callout/utils.ts +++ /dev/null @@ -1,85 +0,0 @@ -// plane helpers -import { sanitizeHTML } from "@plane/helpers"; -// plane ui -import { TEmojiLogoProps } from "@plane/ui"; -// types -import { - EAttributeNames, - TCalloutBlockAttributes, - TCalloutBlockEmojiAttributes, - TCalloutBlockIconAttributes, -} from "./types"; - -export const DEFAULT_CALLOUT_BLOCK_ATTRIBUTES: TCalloutBlockAttributes = { - "data-logo-in-use": "emoji", - "data-icon-color": null, - "data-icon-name": null, - "data-emoji-unicode": "128161", - "data-emoji-url": "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f4a1.png", - "data-background": null, - "data-block-type": "callout-component", -}; - -type TStoredLogoValue = Pick & - (TCalloutBlockEmojiAttributes | TCalloutBlockIconAttributes); - -// function to get the stored logo from local storage -export const getStoredLogo = (): TStoredLogoValue => { - const fallBackValues: TStoredLogoValue = { - "data-logo-in-use": "emoji", - "data-emoji-unicode": DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-emoji-unicode"], - "data-emoji-url": DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-emoji-url"], - }; - - if (typeof window !== "undefined") { - const storedData = sanitizeHTML(localStorage.getItem("editor-calloutComponent-logo")); - if (storedData) { - let parsedData: TEmojiLogoProps; - try { - parsedData = JSON.parse(storedData); - } catch (error) { - console.error(`Error parsing stored callout logo, stored value- ${storedData}`, error); - localStorage.removeItem("editor-calloutComponent-logo"); - return fallBackValues; - } - if (parsedData.in_use === "emoji" && parsedData.emoji?.value) { - return { - "data-logo-in-use": "emoji", - "data-emoji-unicode": parsedData.emoji.value || DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-emoji-unicode"], - "data-emoji-url": parsedData.emoji.url || DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-emoji-url"], - }; - } - if (parsedData.in_use === "icon" && parsedData.icon?.name) { - return { - "data-logo-in-use": "icon", - "data-icon-name": parsedData.icon.name || DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-icon-name"], - "data-icon-color": parsedData.icon.color || DEFAULT_CALLOUT_BLOCK_ATTRIBUTES["data-icon-color"], - }; - } - } - } - // fallback values - return fallBackValues; -}; -// function to update the stored logo on local storage -export const updateStoredLogo = (value: TEmojiLogoProps): void => { - if (typeof window === "undefined") return; - localStorage.setItem("editor-calloutComponent-logo", JSON.stringify(value)); -}; -// function to get the stored background color from local storage -export const getStoredBackgroundColor = (): string | null => { - if (typeof window !== "undefined") { - return sanitizeHTML(localStorage.getItem("editor-calloutComponent-background")); - } - return null; -}; -// function to update the stored background color on local storage -export const updateStoredBackgroundColor = (value: string | null): void => { - if (typeof window === "undefined") return; - if (value === null) { - localStorage.removeItem("editor-calloutComponent-background"); - return; - } else { - localStorage.setItem("editor-calloutComponent-background", value); - } -}; diff --git a/packages/editor/src/core/extensions/core-without-props.ts b/packages/editor/src/core/extensions/core-without-props.ts index 075d90f2d..b1d53aa34 100644 --- a/packages/editor/src/core/extensions/core-without-props.ts +++ b/packages/editor/src/core/extensions/core-without-props.ts @@ -1,3 +1,4 @@ +import { Extensions } from "@tiptap/core"; import TaskItem from "@tiptap/extension-task-item"; import TaskList from "@tiptap/extension-task-list"; import TextStyle from "@tiptap/extension-text-style"; @@ -17,10 +18,10 @@ import { CustomMentionWithoutProps } from "./mentions/mentions-without-props"; import { CustomQuoteExtension } from "./quote"; import { TableHeader, TableCell, TableRow, Table } from "./table"; import { CustomTextAlignExtension } from "./text-align"; -import { CustomCalloutExtensionConfig } from "./callout/extension-config"; import { CustomColorExtension } from "./custom-color"; +import { CoreEditorAdditionalExtensionsWithoutProps } from "@/plane-editor/extensions/core/without-props"; -export const CoreEditorExtensionsWithoutProps = [ +export const CoreEditorExtensionsWithoutProps: Extensions = [ StarterKit.configure({ bulletList: { HTMLAttributes: { @@ -87,8 +88,8 @@ export const CoreEditorExtensionsWithoutProps = [ TableRow, CustomMentionWithoutProps(), CustomTextAlignExtension, - CustomCalloutExtensionConfig, CustomColorExtension, + ...CoreEditorAdditionalExtensionsWithoutProps, ]; export const DocumentEditorExtensionsWithoutProps = [IssueWidgetWithoutProps()]; diff --git a/packages/editor/src/core/extensions/extensions.tsx b/packages/editor/src/core/extensions/extensions.tsx index 959d20e2b..96e90a525 100644 --- a/packages/editor/src/core/extensions/extensions.tsx +++ b/packages/editor/src/core/extensions/extensions.tsx @@ -1,3 +1,4 @@ +import { Extensions } from "@tiptap/core"; import CharacterCount from "@tiptap/extension-character-count"; import Placeholder from "@tiptap/extension-placeholder"; import TaskItem from "@tiptap/extension-task-item"; @@ -8,7 +9,6 @@ import StarterKit from "@tiptap/starter-kit"; import { Markdown } from "tiptap-markdown"; // extensions import { - CustomCalloutExtension, CustomCodeBlockExtension, CustomCodeInlineExtension, CustomCodeMarkPlugin, @@ -33,6 +33,8 @@ import { import { isValidHttpUrl } from "@/helpers/common"; // types import { IMentionHighlight, IMentionSuggestion, TFileHandler } from "@/types"; +// plane editor extensions +import { CoreEditorAdditionalExtensions } from "@/plane-editor/extensions"; type TArguments = { enableHistory: boolean; @@ -45,7 +47,7 @@ type TArguments = { tabIndex?: number; }; -export const CoreEditorExtensions = (args: TArguments) => { +export const CoreEditorExtensions = (args: TArguments): Extensions => { const { enableHistory, fileHandler, mentionConfig, placeholder, tabIndex } = args; return [ @@ -160,7 +162,7 @@ export const CoreEditorExtensions = (args: TArguments) => { }), CharacterCount, CustomTextAlignExtension, - CustomCalloutExtension, CustomColorExtension, + ...CoreEditorAdditionalExtensions(), ]; }; diff --git a/packages/editor/src/core/extensions/index.ts b/packages/editor/src/core/extensions/index.ts index d1fa0ce6d..5821b5ff5 100644 --- a/packages/editor/src/core/extensions/index.ts +++ b/packages/editor/src/core/extensions/index.ts @@ -1,4 +1,3 @@ -export * from "./callout"; export * from "./code"; export * from "./code-inline"; export * from "./custom-image"; diff --git a/packages/editor/src/core/extensions/read-only-extensions.tsx b/packages/editor/src/core/extensions/read-only-extensions.tsx index 2d90592d6..1eb0e8522 100644 --- a/packages/editor/src/core/extensions/read-only-extensions.tsx +++ b/packages/editor/src/core/extensions/read-only-extensions.tsx @@ -1,3 +1,4 @@ +import { Extensions } from "@tiptap/core"; import CharacterCount from "@tiptap/extension-character-count"; import TaskItem from "@tiptap/extension-task-item"; import TaskList from "@tiptap/extension-task-list"; @@ -22,13 +23,14 @@ import { HeadingListExtension, CustomReadOnlyImageExtension, CustomTextAlignExtension, - CustomCalloutReadOnlyExtension, CustomColorExtension, } from "@/extensions"; // helpers import { isValidHttpUrl } from "@/helpers/common"; // types import { IMentionHighlight, TFileHandler } from "@/types"; +// plane editor extensions +import { CoreReadOnlyEditorAdditionalExtensions } from "@/plane-editor/extensions"; type Props = { fileHandler: Pick; @@ -37,7 +39,7 @@ type Props = { }; }; -export const CoreReadOnlyEditorExtensions = (props: Props) => { +export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => { const { fileHandler, mentionConfig } = props; return [ @@ -127,6 +129,6 @@ export const CoreReadOnlyEditorExtensions = (props: Props) => { CustomColorExtension, HeadingListExtension, CustomTextAlignExtension, - CustomCalloutReadOnlyExtension, + ...CoreReadOnlyEditorAdditionalExtensions(), ]; }; diff --git a/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx b/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx index c19bda306..d3fc807d0 100644 --- a/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx +++ b/packages/editor/src/core/extensions/slash-commands/command-items-list.tsx @@ -12,7 +12,6 @@ import { List, ListOrdered, ListTodo, - MessageSquareText, MinusSquare, Table, TextQuote, @@ -35,20 +34,20 @@ import { toggleTextColor, toggleBackgroundColor, insertImage, - insertCallout, setText, } from "@/helpers/editor-commands"; // types -import { CommandProps, ISlashCommandItem } from "@/types"; +import { CommandProps, ISlashCommandItem, TSlashCommandSectionKeys } from "@/types"; +import { TSlashCommandAdditionalOption } from "./root"; export type TSlashCommandSection = { - key: string; + key: TSlashCommandSectionKeys; title?: string; items: ISlashCommandItem[]; }; export const getSlashCommandFilteredSections = - (additionalOptions?: ISlashCommandItem[]) => + (additionalOptions?: TSlashCommandAdditionalOption[]) => ({ query }: { query: string }): TSlashCommandSection[] => { const SLASH_COMMAND_SECTIONS: TSlashCommandSection[] = [ { @@ -180,15 +179,6 @@ export const getSlashCommandFilteredSections = searchTerms: ["img", "photo", "picture", "media", "upload"], command: ({ editor, range }: CommandProps) => insertImage({ editor, event: "insert", range }), }, - { - commandKey: "callout", - key: "callout", - title: "Callout", - icon: , - description: "Insert callout", - searchTerms: ["callout", "comment", "message", "info", "alert"], - command: ({ editor, range }: CommandProps) => insertCallout(editor, range), - }, { commandKey: "divider", key: "divider", @@ -201,7 +191,7 @@ export const getSlashCommandFilteredSections = ], }, { - key: "text-color", + key: "text-colors", title: "Colors", items: [ { @@ -242,7 +232,7 @@ export const getSlashCommandFilteredSections = ], }, { - key: "background-color", + key: "background-colors", title: "Background colors", items: [ { @@ -279,8 +269,18 @@ export const getSlashCommandFilteredSections = }, ]; - additionalOptions?.map((item) => { - SLASH_COMMAND_SECTIONS?.[0]?.items.push(item); + additionalOptions?.forEach((item) => { + const sectionToPushTo = SLASH_COMMAND_SECTIONS.find((s) => s.key === item.section) ?? SLASH_COMMAND_SECTIONS[0]; + const itemIndexToPushAfter = sectionToPushTo.items.findIndex((i) => i.commandKey === item.pushAfter); + if (itemIndexToPushAfter !== undefined) { + const resolvedIndex = + itemIndexToPushAfter + 1 < sectionToPushTo.items.length + ? itemIndexToPushAfter + 1 + : sectionToPushTo.items.length - 1; + sectionToPushTo.items.splice(resolvedIndex, 0, item); + } else { + sectionToPushTo.items.push(item); + } }); const filteredSlashSections = SLASH_COMMAND_SECTIONS.map((section) => ({ diff --git a/packages/editor/src/core/extensions/slash-commands/root.tsx b/packages/editor/src/core/extensions/slash-commands/root.tsx index a99cbc5f9..afe670d8f 100644 --- a/packages/editor/src/core/extensions/slash-commands/root.tsx +++ b/packages/editor/src/core/extensions/slash-commands/root.tsx @@ -3,7 +3,7 @@ import { ReactRenderer } from "@tiptap/react"; import Suggestion, { SuggestionOptions } from "@tiptap/suggestion"; import tippy from "tippy.js"; // types -import { ISlashCommandItem } from "@/types"; +import { ISlashCommandItem, TEditorCommands, TSlashCommandSectionKeys } from "@/types"; // components import { getSlashCommandFilteredSections } from "./command-items-list"; import { SlashCommandsMenu, SlashCommandsMenuProps } from "./command-menu"; @@ -12,6 +12,11 @@ export type SlashCommandOptions = { suggestion: Omit; }; +export type TSlashCommandAdditionalOption = ISlashCommandItem & { + section: TSlashCommandSectionKeys; + pushAfter: TEditorCommands; +}; + const Command = Extension.create({ name: "slash-command", addOptions() { @@ -102,7 +107,7 @@ const renderItems = () => { }; }; -export const SlashCommands = (additionalOptions?: ISlashCommandItem[]) => +export const SlashCommands = (additionalOptions?: TSlashCommandAdditionalOption[]) => Command.configure({ suggestion: { items: getSlashCommandFilteredSections(additionalOptions), diff --git a/packages/editor/src/core/helpers/editor-commands.ts b/packages/editor/src/core/helpers/editor-commands.ts index ec593d536..8b59360a9 100644 --- a/packages/editor/src/core/helpers/editor-commands.ts +++ b/packages/editor/src/core/helpers/editor-commands.ts @@ -189,7 +189,3 @@ export const insertHorizontalRule = (editor: Editor, range?: Range) => { if (range) editor.chain().focus().deleteRange(range).setHorizontalRule().run(); else editor.chain().focus().setHorizontalRule().run(); }; -export const insertCallout = (editor: Editor, range?: Range) => { - if (range) editor.chain().focus().deleteRange(range).insertCallout().run(); - else editor.chain().focus().insertCallout().run(); -}; diff --git a/packages/editor/src/core/types/editor.ts b/packages/editor/src/core/types/editor.ts index 53aae1f26..b3f9a5c6e 100644 --- a/packages/editor/src/core/types/editor.ts +++ b/packages/editor/src/core/types/editor.ts @@ -14,6 +14,8 @@ import { TServerHandler, } from "@/types"; import { TTextAlign } from "@/extensions"; +// plane editor types +import { TEditorAdditionalCommands } from "@/plane-editor/types"; export type TEditorCommands = | "text" @@ -39,7 +41,7 @@ export type TEditorCommands = | "text-color" | "background-color" | "text-align" - | "callout"; + | TEditorAdditionalCommands; export type TCommandExtraProps = { image: { @@ -121,7 +123,7 @@ export interface IEditorProps { onEnterKeyPress?: (e?: any) => void; placeholder?: string | ((isFocused: boolean, value: string) => string); tabIndex?: number; - value?: string | null; + value?: string | null; } export interface ILiteTextEditor extends IEditorProps { extensions?: any[]; diff --git a/packages/editor/src/core/types/slash-commands-suggestion.ts b/packages/editor/src/core/types/slash-commands-suggestion.ts index 91c93203a..d6dfae076 100644 --- a/packages/editor/src/core/types/slash-commands-suggestion.ts +++ b/packages/editor/src/core/types/slash-commands-suggestion.ts @@ -8,6 +8,8 @@ export type CommandProps = { range: Range; }; +export type TSlashCommandSectionKeys = "general" | "text-colors" | "background-colors"; + export type ISlashCommandItem = { commandKey: TEditorCommands; key: string;