forked from github/plane
[WEB-735] chore: added a custom placeholder prop to all the editors (#4194)
* chore: added a custom placeholder prop to all the editors * chore: inbox issue create modal placeholder
This commit is contained in:
parent
77b323f562
commit
abce0ed946
@ -36,11 +36,11 @@
|
||||
"@tiptap/extension-image": "^2.1.13",
|
||||
"@tiptap/extension-list-item": "^2.1.13",
|
||||
"@tiptap/extension-mention": "^2.1.13",
|
||||
"@tiptap/extension-placeholder": "^2.3.0",
|
||||
"@tiptap/extension-task-item": "^2.1.13",
|
||||
"@tiptap/extension-task-list": "^2.1.13",
|
||||
"@tiptap/extension-text-style": "^2.1.13",
|
||||
"@tiptap/extension-underline": "^2.1.13",
|
||||
"prosemirror-codemark": "^0.4.2",
|
||||
"@tiptap/pm": "^2.1.13",
|
||||
"@tiptap/react": "^2.1.13",
|
||||
"@tiptap/starter-kit": "^2.1.13",
|
||||
@ -52,6 +52,7 @@
|
||||
"linkifyjs": "^4.1.3",
|
||||
"lowlight": "^3.0.0",
|
||||
"lucide-react": "^0.294.0",
|
||||
"prosemirror-codemark": "^0.4.2",
|
||||
"react-moveable": "^0.54.2",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
|
@ -34,6 +34,7 @@ interface CustomEditorProps {
|
||||
suggestions?: () => Promise<IMentionSuggestion[]>;
|
||||
};
|
||||
handleEditorReady?: (value: boolean) => void;
|
||||
placeholder?: string | ((isFocused: boolean) => string);
|
||||
}
|
||||
|
||||
export const useEditor = ({
|
||||
@ -51,6 +52,7 @@ export const useEditor = ({
|
||||
restoreFile,
|
||||
handleEditorReady,
|
||||
mentionHandler,
|
||||
placeholder,
|
||||
}: CustomEditorProps) => {
|
||||
const editor = useCustomEditor({
|
||||
editorProps: {
|
||||
@ -58,15 +60,18 @@ export const useEditor = ({
|
||||
...editorProps,
|
||||
},
|
||||
extensions: [
|
||||
...CoreEditorExtensions(
|
||||
{
|
||||
...CoreEditorExtensions({
|
||||
mentionConfig: {
|
||||
mentionSuggestions: mentionHandler.suggestions ?? (() => Promise.resolve<IMentionSuggestion[]>([])),
|
||||
mentionHighlights: mentionHandler.highlights ?? [],
|
||||
},
|
||||
deleteFile,
|
||||
restoreFile,
|
||||
cancelUploadImage
|
||||
),
|
||||
fileConfig: {
|
||||
deleteFile,
|
||||
restoreFile,
|
||||
cancelUploadImage,
|
||||
},
|
||||
placeholder,
|
||||
}),
|
||||
...extensions,
|
||||
],
|
||||
content: typeof initialValue === "string" && initialValue.trim() !== "" ? initialValue : "<p></p>",
|
||||
|
@ -2,6 +2,7 @@ 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 Placeholder from "@tiptap/extension-placeholder";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import { Markdown } from "tiptap-markdown";
|
||||
|
||||
@ -29,15 +30,24 @@ import { CustomTypographyExtension } from "src/ui/extensions/typography";
|
||||
import { CustomHorizontalRule } from "src/ui/extensions/horizontal-rule/horizontal-rule";
|
||||
import { CustomCodeMarkPlugin } from "./custom-code-inline/inline-code-plugin";
|
||||
|
||||
export const CoreEditorExtensions = (
|
||||
type TArguments = {
|
||||
mentionConfig: {
|
||||
mentionSuggestions?: () => Promise<IMentionSuggestion[]>;
|
||||
mentionHighlights?: () => Promise<IMentionHighlight[]>;
|
||||
},
|
||||
deleteFile: DeleteImage,
|
||||
restoreFile: RestoreImage,
|
||||
cancelUploadImage?: () => any
|
||||
) => [
|
||||
};
|
||||
fileConfig: {
|
||||
deleteFile: DeleteImage;
|
||||
restoreFile: RestoreImage;
|
||||
cancelUploadImage?: () => any;
|
||||
};
|
||||
placeholder?: string | ((isFocused: boolean) => string);
|
||||
};
|
||||
|
||||
export const CoreEditorExtensions = ({
|
||||
mentionConfig,
|
||||
fileConfig: { deleteFile, restoreFile, cancelUploadImage },
|
||||
placeholder,
|
||||
}: TArguments) => [
|
||||
StarterKit.configure({
|
||||
bulletList: {
|
||||
HTMLAttributes: {
|
||||
@ -124,4 +134,21 @@ export const CoreEditorExtensions = (
|
||||
mentionHighlights: mentionConfig.mentionHighlights,
|
||||
readonly: false,
|
||||
}),
|
||||
Placeholder.configure({
|
||||
placeholder: ({ editor, node }) => {
|
||||
if (node.type.name === "heading") return `Heading ${node.attrs.level}`;
|
||||
|
||||
const shouldHidePlaceholder =
|
||||
editor.isActive("table") || editor.isActive("codeBlock") || editor.isActive("image");
|
||||
if (shouldHidePlaceholder) return "";
|
||||
|
||||
if (placeholder) {
|
||||
if (typeof placeholder === "string") return placeholder;
|
||||
else return placeholder(editor.isFocused);
|
||||
}
|
||||
|
||||
return "Press '/' for commands...";
|
||||
},
|
||||
includeChildren: true,
|
||||
}),
|
||||
];
|
||||
|
@ -34,7 +34,6 @@
|
||||
"@plane/ui": "*",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@tiptap/core": "^2.1.13",
|
||||
"@tiptap/extension-placeholder": "^2.1.13",
|
||||
"@tiptap/pm": "^2.1.13",
|
||||
"@tiptap/suggestion": "^2.1.13",
|
||||
"lucide-react": "^0.309.0",
|
||||
|
@ -1,28 +1,15 @@
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import { IssueWidgetPlaceholder } from "src/ui/extensions/widgets/issue-embed-widget";
|
||||
|
||||
import { SlashCommand, DragAndDrop } from "@plane/editor-extensions";
|
||||
import { UploadImage } from "@plane/editor-core";
|
||||
|
||||
export const DocumentEditorExtensions = (
|
||||
uploadFile: UploadImage,
|
||||
setHideDragHandle?: (hideDragHandlerFromDragDrop: () => void) => void
|
||||
) => [
|
||||
type TArguments = {
|
||||
uploadFile: UploadImage;
|
||||
setHideDragHandle?: (hideDragHandlerFromDragDrop: () => void) => void;
|
||||
};
|
||||
|
||||
export const DocumentEditorExtensions = ({ uploadFile, setHideDragHandle }: TArguments) => [
|
||||
SlashCommand(uploadFile),
|
||||
DragAndDrop(setHideDragHandle),
|
||||
Placeholder.configure({
|
||||
placeholder: ({ editor, node }) => {
|
||||
if (node.type.name === "heading") {
|
||||
return `Heading ${node.attrs.level}`;
|
||||
}
|
||||
|
||||
if (editor.isActive("table") || editor.isActive("codeBlock") || editor.isActive("image")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "Press '/' for commands...";
|
||||
},
|
||||
includeChildren: true,
|
||||
}),
|
||||
IssueWidgetPlaceholder(),
|
||||
];
|
||||
|
@ -31,6 +31,7 @@ interface IDocumentEditor {
|
||||
suggestions: () => Promise<IMentionSuggestion[]>;
|
||||
};
|
||||
tabIndex?: number;
|
||||
placeholder?: string | ((isFocused: boolean) => string);
|
||||
}
|
||||
|
||||
const DocumentEditor = (props: IDocumentEditor) => {
|
||||
@ -45,6 +46,7 @@ const DocumentEditor = (props: IDocumentEditor) => {
|
||||
handleEditorReady,
|
||||
forwardedRef,
|
||||
tabIndex,
|
||||
placeholder,
|
||||
} = props;
|
||||
// states
|
||||
const [hideDragHandleOnMouseLeave, setHideDragHandleOnMouseLeave] = useState<() => void>(() => {});
|
||||
@ -69,7 +71,11 @@ const DocumentEditor = (props: IDocumentEditor) => {
|
||||
handleEditorReady,
|
||||
forwardedRef,
|
||||
mentionHandler,
|
||||
extensions: DocumentEditorExtensions(fileHandler.upload, setHideDragHandleFunction),
|
||||
extensions: DocumentEditorExtensions({
|
||||
uploadFile: fileHandler.upload,
|
||||
setHideDragHandle: setHideDragHandleFunction,
|
||||
}),
|
||||
placeholder,
|
||||
});
|
||||
|
||||
const editorContainerClassNames = getEditorClassNames({
|
||||
|
@ -32,6 +32,7 @@ export interface ILiteTextEditor {
|
||||
suggestions?: () => Promise<IMentionSuggestion[]>;
|
||||
};
|
||||
tabIndex?: number;
|
||||
placeholder?: string | ((isFocused: boolean) => string);
|
||||
}
|
||||
|
||||
const LiteTextEditor = (props: ILiteTextEditor) => {
|
||||
@ -46,6 +47,7 @@ const LiteTextEditor = (props: ILiteTextEditor) => {
|
||||
onEnterKeyPress,
|
||||
tabIndex,
|
||||
mentionHandler,
|
||||
placeholder = "Add comment...",
|
||||
} = props;
|
||||
|
||||
const editor = useEditor({
|
||||
@ -60,6 +62,7 @@ const LiteTextEditor = (props: ILiteTextEditor) => {
|
||||
forwardedRef,
|
||||
extensions: LiteTextEditorExtensions(onEnterKeyPress),
|
||||
mentionHandler,
|
||||
placeholder,
|
||||
});
|
||||
|
||||
const editorContainerClassName = getEditorClassNames({
|
||||
|
@ -32,7 +32,6 @@
|
||||
"@plane/editor-core": "*",
|
||||
"@plane/editor-extensions": "*",
|
||||
"@tiptap/core": "^2.1.13",
|
||||
"@tiptap/extension-placeholder": "^2.1.13",
|
||||
"lucide-react": "^0.294.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,26 +1,13 @@
|
||||
import { UploadImage } from "@plane/editor-core";
|
||||
import { DragAndDrop, SlashCommand } from "@plane/editor-extensions";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
|
||||
export const RichTextEditorExtensions = (
|
||||
uploadFile: UploadImage,
|
||||
dragDropEnabled?: boolean,
|
||||
setHideDragHandle?: (hideDragHandlerFromDragDrop: () => void) => void
|
||||
) => [
|
||||
type TArguments = {
|
||||
uploadFile: UploadImage;
|
||||
dragDropEnabled?: boolean;
|
||||
setHideDragHandle?: (hideDragHandlerFromDragDrop: () => void) => void;
|
||||
};
|
||||
|
||||
export const RichTextEditorExtensions = ({ uploadFile, dragDropEnabled, setHideDragHandle }: TArguments) => [
|
||||
SlashCommand(uploadFile),
|
||||
dragDropEnabled === true && DragAndDrop(setHideDragHandle),
|
||||
Placeholder.configure({
|
||||
placeholder: ({ editor, node }) => {
|
||||
if (node.type.name === "heading") {
|
||||
return `Heading ${node.attrs.level}`;
|
||||
}
|
||||
|
||||
if (editor.isActive("table") || editor.isActive("codeBlock") || editor.isActive("image")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "Press '/' for commands...";
|
||||
},
|
||||
includeChildren: true,
|
||||
}),
|
||||
];
|
||||
|
@ -35,6 +35,7 @@ export type IRichTextEditor = {
|
||||
highlights: () => Promise<IMentionHighlight[]>;
|
||||
suggestions: () => Promise<IMentionSuggestion[]>;
|
||||
};
|
||||
placeholder?: string | ((isFocused: boolean) => string);
|
||||
tabIndex?: number;
|
||||
};
|
||||
|
||||
@ -50,6 +51,7 @@ const RichTextEditor = (props: IRichTextEditor) => {
|
||||
forwardedRef,
|
||||
// rerenderOnPropsChange,
|
||||
id = "",
|
||||
placeholder,
|
||||
tabIndex,
|
||||
mentionHandler,
|
||||
} = props;
|
||||
@ -74,8 +76,13 @@ const RichTextEditor = (props: IRichTextEditor) => {
|
||||
value,
|
||||
forwardedRef,
|
||||
// rerenderOnPropsChange,
|
||||
extensions: RichTextEditorExtensions(fileHandler.upload, dragDropEnabled, setHideDragHandleFunction),
|
||||
extensions: RichTextEditorExtensions({
|
||||
uploadFile: fileHandler.upload,
|
||||
dragDropEnabled,
|
||||
setHideDragHandle: setHideDragHandleFunction,
|
||||
}),
|
||||
mentionHandler,
|
||||
placeholder,
|
||||
});
|
||||
|
||||
const editorContainerClassName = getEditorClassNames({
|
||||
|
@ -38,8 +38,10 @@ export const InboxIssueDescription: FC<TInboxIssueDescription> = observer((props
|
||||
workspaceId={workspaceId}
|
||||
projectId={projectId}
|
||||
dragDropEnabled={false}
|
||||
onChange={(_description: object, description_html: string) => {
|
||||
handleData("description_html", description_html);
|
||||
onChange={(_description: object, description_html: string) => handleData("description_html", description_html)}
|
||||
placeholder={(isFocused) => {
|
||||
if (isFocused) return "Press '/' for commands...";
|
||||
else return "Click to add description";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -19,6 +19,7 @@ export type IssueDescriptionInputProps = {
|
||||
initialValue: string | undefined;
|
||||
disabled?: boolean;
|
||||
issueOperations: TIssueOperations;
|
||||
placeholder?: string | ((isFocused: boolean) => string);
|
||||
setIsSubmitting: (initialValue: "submitting" | "submitted" | "saved") => void;
|
||||
swrIssueDescription: string | null | undefined;
|
||||
};
|
||||
@ -33,6 +34,7 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((p
|
||||
initialValue,
|
||||
issueOperations,
|
||||
setIsSubmitting,
|
||||
placeholder,
|
||||
} = props;
|
||||
|
||||
const { handleSubmit, reset, control } = useForm<TIssue>({
|
||||
@ -103,6 +105,14 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((p
|
||||
onChange(description_html);
|
||||
debouncedFormSave();
|
||||
}}
|
||||
placeholder={
|
||||
placeholder
|
||||
? placeholder
|
||||
: (isFocused) => {
|
||||
if (isFocused) return "Press '/' for commands...";
|
||||
else return "Click to add description";
|
||||
}
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<RichTextReadOnlyEditor
|
||||
|
@ -477,6 +477,10 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
}}
|
||||
ref={editorRef}
|
||||
tabIndex={getTabIndex("description_html")}
|
||||
placeholder={(isFocused) => {
|
||||
if (isFocused) return "Press '/' for commands...";
|
||||
else return "Click to add description";
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
29
yarn.lock
29
yarn.lock
@ -2477,10 +2477,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.1.13.tgz#30f8ae3f8833c606b339f3554b9ffdbe1e604463"
|
||||
integrity sha512-cEoZBJrsQn69FPpUMePXG/ltGXtqKISgypj70PEHXt5meKDjpmMVSY4/8cXvFYEYsI9GvIwyAK0OrfAHiSoROA==
|
||||
|
||||
"@tiptap/extension-placeholder@^2.1.13":
|
||||
version "2.1.13"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.1.13.tgz#b735591f719b9fe89c90dcc6327d2ef2851be510"
|
||||
integrity sha512-vIY7y7UbqsrAW/y8bDE9eRenbQEU16kNHB5Wri8RU1YiUZpkPgdXP/pLqyjIIq95SwP/vdTIHjHoQ77VLRl1hA==
|
||||
"@tiptap/extension-placeholder@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.3.0.tgz#db1f4375c8365c491211457c6d13f6efa486cd3a"
|
||||
integrity sha512-1BOyxVLzyUYf6yOOeJ8CfpP6DSCS4L6HjBZqj6WP1z1NyBV8RAfhf3UuLNcimfSWAETXFR3g0ZbaxxWffI1cEg==
|
||||
|
||||
"@tiptap/extension-strike@^2.1.13":
|
||||
version "2.1.13"
|
||||
@ -7854,16 +7854,8 @@ streamx@^2.15.0:
|
||||
fast-fifo "^1.1.0"
|
||||
queue-tick "^1.0.1"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
||||
name string-width-cjs
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@ -7939,14 +7931,7 @@ stringify-object@^3.3.0:
|
||||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
Loading…
Reference in New Issue
Block a user