[WIKI-542] refactor: editor extensions (#7345)

* refactor: editor extensions

* fix: placeholder extension
This commit is contained in:
Aaryan Khandelwal
2025-07-14 17:10:32 +05:30
committed by GitHub
parent 2c70c1aaa8
commit c067eaa1ed
7 changed files with 117 additions and 153 deletions

View File

@@ -118,4 +118,7 @@ export const CustomCodeBlockExtension = CodeBlockLowlight.extend({
lowlight,
defaultLanguage: "plaintext",
exitOnTripleEnter: false,
HTMLAttributes: {
class: "",
},
});

View File

@@ -3,6 +3,8 @@ import { Plugin } from "@tiptap/pm/state";
import { find, registerCustomProtocol, reset } from "linkifyjs";
// constants
import { CORE_EXTENSIONS } from "@/constants/extension";
// helpers
import { isValidHttpUrl } from "@/helpers/common";
// local imports
import { autolink } from "./helpers/autolink";
import { clickHandler } from "./helpers/clickHandler";
@@ -112,13 +114,14 @@ export const CustomLinkExtension = Mark.create<LinkOptions, CustomLinkStorage>({
linkOnPaste: true,
autolink: true,
inclusive: false,
protocols: [],
protocols: ["http", "https"],
HTMLAttributes: {
target: "_blank",
rel: "noopener noreferrer nofollow",
class: null,
class:
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
},
validate: undefined,
validate: (url: string) => isValidHttpUrl(url).isValid,
};
},

View File

@@ -1,14 +1,10 @@
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";
import TaskList from "@tiptap/extension-task-list";
import TextStyle from "@tiptap/extension-text-style";
import TiptapUnderline from "@tiptap/extension-underline";
import StarterKit from "@tiptap/starter-kit";
import { Markdown } from "tiptap-markdown";
// constants
import { CORE_EXTENSIONS } from "@/constants/extension";
// extensions
import {
CustomCalloutExtension,
@@ -30,9 +26,6 @@ import {
TableRow,
UtilityExtension,
} from "@/extensions";
// helpers
import { isValidHttpUrl } from "@/helpers/common";
import { getExtensionStorage } from "@/helpers/get-extension-storage";
// plane editor extensions
import { CoreEditorAdditionalExtensions } from "@/plane-editor/extensions";
// types
@@ -40,6 +33,8 @@ import type { IEditorProps } from "@/types";
// local imports
import { CustomImageExtension } from "./custom-image/extension";
import { EmojiExtension } from "./emoji/extension";
import { CustomPlaceholderExtension } from "./placeholder";
import { CustomStarterKitExtension } from "./starter-kit";
type TArguments = Pick<
IEditorProps,
@@ -62,62 +57,15 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
} = args;
const extensions = [
StarterKit.configure({
bulletList: {
HTMLAttributes: {
class: "list-disc pl-7 space-y-[--list-spacing-y]",
},
},
orderedList: {
HTMLAttributes: {
class: "list-decimal pl-7 space-y-[--list-spacing-y]",
},
},
listItem: {
HTMLAttributes: {
class: "not-prose space-y-2",
},
},
code: false,
codeBlock: false,
horizontalRule: false,
blockquote: false,
paragraph: {
HTMLAttributes: {
class: "editor-paragraph-block",
},
},
heading: {
HTMLAttributes: {
class: "editor-heading-block",
},
},
dropcursor: {
class:
"text-custom-text-300 transition-all motion-reduce:transition-none motion-reduce:hover:transform-none duration-200 ease-[cubic-bezier(0.165, 0.84, 0.44, 1)]",
},
...(enableHistory ? {} : { history: false }),
CustomStarterKitExtension({
enableHistory,
}),
EmojiExtension,
CustomQuoteExtension,
CustomHorizontalRule.configure({
HTMLAttributes: {
class: "py-4 border-custom-border-400",
},
}),
CustomHorizontalRule,
CustomKeymap,
ListKeymap({ tabIndex }),
CustomLinkExtension.configure({
openOnClick: true,
autolink: true,
linkOnPaste: true,
protocols: ["http", "https"],
validate: (url: string) => isValidHttpUrl(url).isValid,
HTMLAttributes: {
class:
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
},
}),
CustomLinkExtension,
CustomTypographyExtension,
TiptapUnderline,
TextStyle,
@@ -132,11 +80,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
},
nested: true,
}),
CustomCodeBlockExtension.configure({
HTMLAttributes: {
class: "",
},
}),
CustomCodeBlockExtension,
CustomCodeInlineExtension,
Markdown.configure({
html: true,
@@ -149,34 +93,9 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
TableCell,
TableRow,
CustomMentionExtension(mentionHandler),
Placeholder.configure({
placeholder: ({ editor, node }) => {
if (!editor.isEditable) return "";
if (node.type.name === CORE_EXTENSIONS.HEADING) return `Heading ${node.attrs.level}`;
const isUploadInProgress = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress;
if (isUploadInProgress) return "";
const shouldHidePlaceholder =
editor.isActive(CORE_EXTENSIONS.TABLE) ||
editor.isActive(CORE_EXTENSIONS.CODE_BLOCK) ||
editor.isActive(CORE_EXTENSIONS.IMAGE) ||
editor.isActive(CORE_EXTENSIONS.CUSTOM_IMAGE);
if (shouldHidePlaceholder) return "";
if (placeholder) {
if (typeof placeholder === "string") return placeholder;
else return placeholder(editor.isFocused, editor.getHTML());
}
return "Press '/' for commands...";
},
includeChildren: true,
}),
CustomPlaceholderExtension({ placeholder }),
CharacterCount,
CustomColorExtension,
CustomTextAlignExtension,
CustomCalloutExtension,
UtilityExtension({
@@ -184,7 +103,6 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
fileHandler,
isEditable: editable,
}),
CustomColorExtension,
...CoreEditorAdditionalExtensions({
disabledExtensions,
flaggedExtensions,

View File

@@ -20,15 +20,16 @@ declare module "@tiptap/core" {
export const CustomHorizontalRule = Node.create<HorizontalRuleOptions>({
name: CORE_EXTENSIONS.HORIZONTAL_RULE,
group: "block",
addOptions() {
return {
HTMLAttributes: {},
HTMLAttributes: {
class: "py-4 border-custom-border-400",
},
};
},
group: "block",
parseHTML() {
return [
{

View File

@@ -0,0 +1,43 @@
import Placeholder from "@tiptap/extension-placeholder";
// constants
import { CORE_EXTENSIONS } from "@/constants/extension";
// helpers
import { getExtensionStorage } from "@/helpers/get-extension-storage";
// types
import type { IEditorProps } from "@/types";
type TArgs = {
placeholder: IEditorProps["placeholder"];
};
export const CustomPlaceholderExtension = (args: TArgs) => {
const { placeholder } = args;
return Placeholder.configure({
placeholder: ({ editor, node }) => {
if (!editor.isEditable) return "";
if (node.type.name === CORE_EXTENSIONS.HEADING) return `Heading ${node.attrs.level}`;
const isUploadInProgress = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY)?.uploadInProgress;
if (isUploadInProgress) return "";
const shouldHidePlaceholder =
editor.isActive(CORE_EXTENSIONS.TABLE) ||
editor.isActive(CORE_EXTENSIONS.CODE_BLOCK) ||
editor.isActive(CORE_EXTENSIONS.IMAGE) ||
editor.isActive(CORE_EXTENSIONS.CUSTOM_IMAGE);
if (shouldHidePlaceholder) return "";
if (placeholder) {
if (typeof placeholder === "string") return placeholder;
else return placeholder(editor.isFocused, editor.getHTML());
}
return "Press '/' for commands...";
},
includeChildren: true,
});
};

View File

@@ -4,7 +4,6 @@ import TaskItem from "@tiptap/extension-task-item";
import TaskList from "@tiptap/extension-task-list";
import TextStyle from "@tiptap/extension-text-style";
import TiptapUnderline from "@tiptap/extension-underline";
import StarterKit from "@tiptap/starter-kit";
import { Markdown } from "tiptap-markdown";
// extensions
import {
@@ -25,8 +24,6 @@ import {
UtilityExtension,
ImageExtension,
} from "@/extensions";
// helpers
import { isValidHttpUrl } from "@/helpers/common";
// plane editor extensions
import { CoreReadOnlyEditorAdditionalExtensions } from "@/plane-editor/extensions";
// types
@@ -34,6 +31,7 @@ import type { IReadOnlyEditorProps } from "@/types";
// local imports
import { CustomImageExtension } from "./custom-image/extension";
import { EmojiExtension } from "./emoji/extension";
import { CustomStarterKitExtension } from "./starter-kit";
type Props = Pick<IReadOnlyEditorProps, "disabledExtensions" | "flaggedExtensions" | "fileHandler" | "mentionHandler">;
@@ -41,57 +39,13 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
const { disabledExtensions, fileHandler, flaggedExtensions, mentionHandler } = props;
const extensions = [
StarterKit.configure({
bulletList: {
HTMLAttributes: {
class: "list-disc pl-7 space-y-[--list-spacing-y]",
},
},
orderedList: {
HTMLAttributes: {
class: "list-decimal pl-7 space-y-[--list-spacing-y]",
},
},
listItem: {
HTMLAttributes: {
class: "not-prose space-y-2",
},
},
code: false,
codeBlock: false,
horizontalRule: false,
blockquote: false,
paragraph: {
HTMLAttributes: {
class: "editor-paragraph-block",
},
},
heading: {
HTMLAttributes: {
class: "editor-heading-block",
},
},
dropcursor: false,
gapcursor: false,
CustomStarterKitExtension({
enableHistory: false,
}),
EmojiExtension,
CustomQuoteExtension,
CustomHorizontalRule.configure({
HTMLAttributes: {
class: "py-4 border-custom-border-400",
},
}),
CustomLinkExtension.configure({
openOnClick: true,
autolink: true,
linkOnPaste: true,
protocols: ["http", "https"],
validate: (url: string) => isValidHttpUrl(url).isValid,
HTMLAttributes: {
class:
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
},
}),
CustomHorizontalRule,
CustomLinkExtension,
CustomTypographyExtension,
TiptapUnderline,
TextStyle,
@@ -106,11 +60,7 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
},
nested: true,
}),
CustomCodeBlockExtension.configure({
HTMLAttributes: {
class: "",
},
}),
CustomCodeBlockExtension,
CustomCodeInlineExtension,
Markdown.configure({
html: true,

View File

@@ -0,0 +1,46 @@
import StarterKit from "@tiptap/starter-kit";
type TArgs = {
enableHistory: boolean;
};
export const CustomStarterKitExtension = (args: TArgs) => {
const { enableHistory } = args;
return StarterKit.configure({
bulletList: {
HTMLAttributes: {
class: "list-disc pl-7 space-y-[--list-spacing-y]",
},
},
orderedList: {
HTMLAttributes: {
class: "list-decimal pl-7 space-y-[--list-spacing-y]",
},
},
listItem: {
HTMLAttributes: {
class: "not-prose space-y-2",
},
},
code: false,
codeBlock: false,
horizontalRule: false,
blockquote: false,
paragraph: {
HTMLAttributes: {
class: "editor-paragraph-block",
},
},
heading: {
HTMLAttributes: {
class: "editor-heading-block",
},
},
dropcursor: {
class:
"text-custom-text-300 transition-all motion-reduce:transition-none motion-reduce:hover:transform-none duration-200 ease-[cubic-bezier(0.165, 0.84, 0.44, 1)]",
},
...(enableHistory ? {} : { history: false }),
});
};