mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
style: lite text editor editor toolbar (#2601)
* style: comment editor toolbar * style: updated icon styling
This commit is contained in:
parent
5b808571e5
commit
c394a4f64e
@ -7,7 +7,11 @@ interface EditorContainerProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const EditorContainer = ({ editor, editorClassNames, children }: EditorContainerProps) => (
|
||||
export const EditorContainer = ({
|
||||
editor,
|
||||
editorClassNames,
|
||||
children,
|
||||
}: EditorContainerProps) => (
|
||||
<div
|
||||
id="editor-container"
|
||||
onClick={() => {
|
||||
|
@ -1,14 +1,14 @@
|
||||
"use client"
|
||||
import * as React from 'react';
|
||||
"use client";
|
||||
import * as React from "react";
|
||||
import { Extension } from "@tiptap/react";
|
||||
import { UploadImage } from '../types/upload-image';
|
||||
import { DeleteImage } from '../types/delete-image';
|
||||
import { getEditorClassNames } from '../lib/utils';
|
||||
import { EditorProps } from '@tiptap/pm/view';
|
||||
import { useEditor } from './hooks/useEditor';
|
||||
import { EditorContainer } from '../ui/components/editor-container';
|
||||
import { EditorContentWrapper } from '../ui/components/editor-content';
|
||||
import { IMentionSuggestion } from '../types/mention-suggestion';
|
||||
import { UploadImage } from "../types/upload-image";
|
||||
import { DeleteImage } from "../types/delete-image";
|
||||
import { getEditorClassNames } from "../lib/utils";
|
||||
import { EditorProps } from "@tiptap/pm/view";
|
||||
import { useEditor } from "./hooks/useEditor";
|
||||
import { EditorContainer } from "../ui/components/editor-container";
|
||||
import { EditorContentWrapper } from "../ui/components/editor-content";
|
||||
import { IMentionSuggestion } from "../types/mention-suggestion";
|
||||
|
||||
interface ICoreEditor {
|
||||
value: string;
|
||||
@ -19,7 +19,9 @@ interface ICoreEditor {
|
||||
customClassName?: string;
|
||||
editorContentCustomClassNames?: string;
|
||||
onChange?: (json: any, html: string) => void;
|
||||
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
|
||||
setIsSubmitting?: (
|
||||
isSubmitting: "submitting" | "submitted" | "saved",
|
||||
) => void;
|
||||
setShouldShowAlert?: (showAlert: boolean) => void;
|
||||
editable?: boolean;
|
||||
forwardedRef?: any;
|
||||
@ -72,22 +74,29 @@ const CoreEditor = ({
|
||||
forwardedRef,
|
||||
});
|
||||
|
||||
const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
|
||||
const editorClassNames = getEditorClassNames({
|
||||
noBorder,
|
||||
borderOnFocus,
|
||||
customClassName,
|
||||
});
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
return (
|
||||
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
|
||||
<div className="flex flex-col">
|
||||
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
|
||||
<EditorContentWrapper
|
||||
editor={editor}
|
||||
editorContentCustomClassNames={editorContentCustomClassNames}
|
||||
/>
|
||||
</div>
|
||||
</EditorContainer >
|
||||
</EditorContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const CoreEditorWithRef = React.forwardRef<EditorHandle, ICoreEditor>((props, ref) => (
|
||||
<CoreEditor {...props} forwardedRef={ref} />
|
||||
));
|
||||
const CoreEditorWithRef = React.forwardRef<EditorHandle, ICoreEditor>(
|
||||
(props, ref) => <CoreEditor {...props} forwardedRef={ref} />,
|
||||
);
|
||||
|
||||
CoreEditorWithRef.displayName = "CoreEditorWithRef";
|
||||
|
||||
|
@ -18,9 +18,9 @@ export type IMentionSuggestion = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
redirect_uri: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type IMentionHighlight = string
|
||||
export type IMentionHighlight = string;
|
||||
|
||||
interface ILiteTextEditor {
|
||||
value: string;
|
||||
@ -32,7 +32,7 @@ interface ILiteTextEditor {
|
||||
editorContentCustomClassNames?: string;
|
||||
onChange?: (json: any, html: string) => void;
|
||||
setIsSubmitting?: (
|
||||
isSubmitting: "submitting" | "submitted" | "saved"
|
||||
isSubmitting: "submitting" | "submitted" | "saved",
|
||||
) => void;
|
||||
setShouldShowAlert?: (showAlert: boolean) => void;
|
||||
forwardedRef?: any;
|
||||
@ -50,6 +50,7 @@ interface ILiteTextEditor {
|
||||
onEnterKeyPress?: (e?: any) => void;
|
||||
mentionHighlights?: string[];
|
||||
mentionSuggestions?: IMentionSuggestion[];
|
||||
submitButton?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface LiteTextEditorProps extends ILiteTextEditor {
|
||||
@ -61,24 +62,27 @@ interface EditorHandle {
|
||||
setEditorValue: (content: string) => void;
|
||||
}
|
||||
|
||||
const LiteTextEditor = ({
|
||||
onChange,
|
||||
debouncedUpdatesEnabled,
|
||||
setIsSubmitting,
|
||||
setShouldShowAlert,
|
||||
editorContentCustomClassNames,
|
||||
value,
|
||||
uploadFile,
|
||||
deleteFile,
|
||||
noBorder,
|
||||
borderOnFocus,
|
||||
customClassName,
|
||||
forwardedRef,
|
||||
commentAccessSpecifier,
|
||||
onEnterKeyPress,
|
||||
mentionHighlights,
|
||||
mentionSuggestions
|
||||
}: LiteTextEditorProps) => {
|
||||
const LiteTextEditor = (props: LiteTextEditorProps) => {
|
||||
const {
|
||||
onChange,
|
||||
debouncedUpdatesEnabled,
|
||||
setIsSubmitting,
|
||||
setShouldShowAlert,
|
||||
editorContentCustomClassNames,
|
||||
value,
|
||||
uploadFile,
|
||||
deleteFile,
|
||||
noBorder,
|
||||
borderOnFocus,
|
||||
customClassName,
|
||||
forwardedRef,
|
||||
commentAccessSpecifier,
|
||||
onEnterKeyPress,
|
||||
mentionHighlights,
|
||||
mentionSuggestions,
|
||||
submitButton,
|
||||
} = props;
|
||||
|
||||
const editor = useEditor({
|
||||
onChange,
|
||||
debouncedUpdatesEnabled,
|
||||
@ -90,7 +94,7 @@ const LiteTextEditor = ({
|
||||
forwardedRef,
|
||||
extensions: LiteTextEditorExtensions(onEnterKeyPress),
|
||||
mentionHighlights,
|
||||
mentionSuggestions
|
||||
mentionSuggestions,
|
||||
});
|
||||
|
||||
const editorClassNames = getEditorClassNames({
|
||||
@ -114,6 +118,7 @@ const LiteTextEditor = ({
|
||||
uploadFile={uploadFile}
|
||||
setIsSubmitting={setIsSubmitting}
|
||||
commentAccessSpecifier={commentAccessSpecifier}
|
||||
submitButton={submitButton}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -122,7 +127,7 @@ const LiteTextEditor = ({
|
||||
};
|
||||
|
||||
const LiteTextEditorWithRef = React.forwardRef<EditorHandle, ILiteTextEditor>(
|
||||
(props, ref) => <LiteTextEditor {...props} forwardedRef={ref} />
|
||||
(props, ref) => <LiteTextEditor {...props} forwardedRef={ref} />,
|
||||
);
|
||||
|
||||
LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Editor } from "@tiptap/react";
|
||||
import { BoldIcon, LucideIcon } from "lucide-react";
|
||||
import { BoldIcon } from "lucide-react";
|
||||
|
||||
import {
|
||||
BoldItem,
|
||||
@ -14,7 +14,6 @@ import {
|
||||
TableItem,
|
||||
UnderLineItem,
|
||||
} from "@plane/editor-core";
|
||||
import { Icon } from "./icon";
|
||||
import { Tooltip } from "../../tooltip";
|
||||
import { UploadImage } from "../..";
|
||||
|
||||
@ -41,8 +40,9 @@ type EditorBubbleMenuProps = {
|
||||
};
|
||||
uploadFile: UploadImage;
|
||||
setIsSubmitting?: (
|
||||
isSubmitting: "submitting" | "submitted" | "saved"
|
||||
isSubmitting: "submitting" | "submitted" | "saved",
|
||||
) => void;
|
||||
submitButton: React.ReactNode;
|
||||
};
|
||||
|
||||
export const FixedMenu = (props: EditorBubbleMenuProps) => {
|
||||
@ -72,116 +72,132 @@ export const FixedMenu = (props: EditorBubbleMenuProps) => {
|
||||
props.commentAccessSpecifier?.onAccessChange(accessKey);
|
||||
};
|
||||
|
||||
console.log(complexItems);
|
||||
|
||||
return (
|
||||
<div className="flex w-fit divide-x divide-custom-border-300 rounded border border-custom-border-300 bg-custom-background-100 shadow-xl">
|
||||
<div className="flex items-stretch gap-1.5 w-full h-9">
|
||||
{props.commentAccessSpecifier && (
|
||||
<div className="flex border border-custom-border-300 mt-0 divide-x divide-custom-border-300 rounded overflow-hidden">
|
||||
<div className="flex-shrink-0 flex items-stretch gap-0.5 border border-custom-border-200 rounded p-1">
|
||||
{props?.commentAccessSpecifier.commentAccess?.map((access) => (
|
||||
<Tooltip key={access.key} tooltipContent={access.label}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAccessChange(access.key)}
|
||||
className={`grid place-basicMarkItems-center p-1 hover:bg-custom-background-80 ${
|
||||
className={`aspect-square grid place-items-center p-1 rounded-sm hover:bg-custom-background-90 ${
|
||||
props.commentAccessSpecifier?.accessValue === access.key
|
||||
? "bg-custom-background-80"
|
||||
? "bg-custom-background-90"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<access.icon
|
||||
className={`w-4 h-4 ${
|
||||
className={`w-3.5 h-3.5 ${
|
||||
props.commentAccessSpecifier?.accessValue === access.key
|
||||
? "!text-custom-text-100"
|
||||
: "!text-custom-text-400"
|
||||
? "text-custom-text-100"
|
||||
: "text-custom-text-400"
|
||||
}`}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex">
|
||||
{basicMarkItems.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={item.command}
|
||||
className={cn(
|
||||
"p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
|
||||
{
|
||||
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(),
|
||||
}
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={cn("h-4 w-4", {
|
||||
"text-custom-text-100": item.isActive(),
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex">
|
||||
{listItems.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={item.command}
|
||||
className={cn(
|
||||
"p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
|
||||
{
|
||||
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(),
|
||||
}
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={cn("h-4 w-4", {
|
||||
"text-custom-text-100": item.isActive(),
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex">
|
||||
{userActionItems.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={item.command}
|
||||
className={cn(
|
||||
"p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
|
||||
{
|
||||
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(),
|
||||
}
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={cn("h-4 w-4", {
|
||||
"text-custom-text-100": item.isActive(),
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex">
|
||||
{complexItems.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={item.command}
|
||||
className={cn(
|
||||
"p-2 text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5 transition-colors",
|
||||
{
|
||||
"text-custom-text-100 bg-custom-primary-100/5": item.isActive(),
|
||||
}
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={cn("h-4 w-4", {
|
||||
"text-custom-text-100": item.isActive(),
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
<div className="flex items-stretch justify-between gap-2 w-full border border-custom-border-200 bg-custom-background-90 rounded p-1">
|
||||
<div className="flex items-stretch">
|
||||
<div className="flex items-stretch gap-0.5 pr-2.5 border-r border-custom-border-200">
|
||||
{basicMarkItems.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={item.command}
|
||||
className={cn(
|
||||
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
|
||||
{
|
||||
"text-custom-text-100 bg-custom-background-80":
|
||||
item.isActive(),
|
||||
},
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={cn("h-3.5 w-3.5", {
|
||||
"text-custom-text-100": item.isActive(),
|
||||
})}
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200">
|
||||
{listItems.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={item.command}
|
||||
className={cn(
|
||||
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
|
||||
{
|
||||
"text-custom-text-100 bg-custom-background-80":
|
||||
item.isActive(),
|
||||
},
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={cn("h-3.5 w-3.5", {
|
||||
"text-custom-text-100": item.isActive(),
|
||||
})}
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-stretch gap-0.5 px-2.5 border-r border-custom-border-200">
|
||||
{userActionItems.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={item.command}
|
||||
className={cn(
|
||||
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
|
||||
{
|
||||
"text-custom-text-100 bg-custom-background-80":
|
||||
item.isActive(),
|
||||
},
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={cn("h-3.5 w-3.5", {
|
||||
"text-custom-text-100": item.isActive(),
|
||||
})}
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-stretch gap-0.5 pl-2.5">
|
||||
{complexItems.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={item.command}
|
||||
className={cn(
|
||||
"p-1 aspect-square text-custom-text-400 hover:bg-custom-background-80 rounded-sm grid place-items-center",
|
||||
{
|
||||
"text-custom-text-100 bg-custom-background-80":
|
||||
item.isActive(),
|
||||
},
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={cn("h-3.5 w-3.5", {
|
||||
"text-custom-text-100": item.isActive(),
|
||||
})}
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{props.submitButton}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -49,7 +49,7 @@ export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug, projectId)
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug, projectId);
|
||||
|
||||
const {
|
||||
formState: { isSubmitting },
|
||||
|
@ -54,7 +54,7 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined)
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined);
|
||||
|
||||
const {
|
||||
control,
|
||||
@ -75,7 +75,7 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
|
||||
return (
|
||||
<form onSubmit={handleSubmit(handleAddComment)}>
|
||||
<div className="space-y-2">
|
||||
<div className="relative">
|
||||
<div className="relative h-full">
|
||||
{showAccessSpecifier && (
|
||||
<div className="absolute bottom-2 left-3 z-[1]">
|
||||
<Controller
|
||||
@ -119,22 +119,28 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
|
||||
deleteFile={fileService.deleteImage}
|
||||
ref={editorRef}
|
||||
value={!commentValue || commentValue === "" ? "<p></p>" : commentValue}
|
||||
customClassName="p-3 min-h-[100px] shadow-sm"
|
||||
customClassName="p-2 h-full"
|
||||
debouncedUpdatesEnabled={false}
|
||||
mentionSuggestions={editorSuggestions.mentionSuggestions}
|
||||
mentionHighlights={editorSuggestions.mentionHighlights}
|
||||
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
|
||||
commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }}
|
||||
submitButton={
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
className="!px-2.5 !py-1.5 !text-xs"
|
||||
disabled={isSubmitting || disabled}
|
||||
>
|
||||
{isSubmitting ? "Adding..." : "Comment"}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button variant="neutral-primary" type="submit" disabled={isSubmitting || disabled}>
|
||||
{isSubmitting ? "Adding..." : "Comment"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user