mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
[WIKI-542] refactor: editor extensions (#7345)
* refactor: editor extensions * fix: placeholder extension
This commit is contained in:
committed by
GitHub
parent
2c70c1aaa8
commit
c067eaa1ed
@@ -118,4 +118,7 @@ export const CustomCodeBlockExtension = CodeBlockLowlight.extend({
|
||||
lowlight,
|
||||
defaultLanguage: "plaintext",
|
||||
exitOnTripleEnter: false,
|
||||
HTMLAttributes: {
|
||||
class: "",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 [
|
||||
{
|
||||
|
||||
43
packages/editor/src/core/extensions/placeholder.ts
Normal file
43
packages/editor/src/core/extensions/placeholder.ts
Normal 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,
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
46
packages/editor/src/core/extensions/starter-kit.ts
Normal file
46
packages/editor/src/core/extensions/starter-kit.ts
Normal 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 }),
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user