[PE-93] refactor: editor mentions extension (#6178)

* refactor: editor mentions

* fix: build errors

* fix: build errors

* chore: add cycle status to search endpoint response

* fix: build errors

* fix: dynamic mention content in markdown

* chore: update entity search endpoint

* style: user mention popover

* chore: edition specific mention content handler

* chore: show deactivated user for old mentions

* chore: update search entity keys

* refactor: use editor mention hook
This commit is contained in:
Aaryan Khandelwal 2024-12-20 13:41:25 +05:30 committed by GitHub
parent c10b875e2a
commit 119d343d5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
78 changed files with 1491 additions and 992 deletions

View file

@ -0,0 +1 @@
export * from "./mentions";

View file

@ -0,0 +1 @@
export * from "./root";

View file

@ -0,0 +1,17 @@
// plane editor
import { TMentionComponentProps } from "@plane/editor";
// plane web components
import { EditorAdditionalMentionsRoot } from "@/plane-web/components/editor";
// local components
import { EditorUserMention } from "./user";
export const EditorMentionsRoot: React.FC<TMentionComponentProps> = (props) => {
const { entity_identifier, entity_name } = props;
switch (entity_name) {
case "user_mention":
return <EditorUserMention id={entity_identifier} />;
default:
return <EditorAdditionalMentionsRoot {...props} />;
}
};

View file

@ -0,0 +1,41 @@
import { observer } from "mobx-react";
import Link from "next/link";
import { useParams } from "next/navigation";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useMember, useUser } from "@/hooks/store";
type Props = {
id: string;
};
export const EditorUserMention: React.FC<Props> = observer((props) => {
const { id } = props;
// params
const { workspaceSlug } = useParams();
// store hooks
const { data: currentUser } = useUser();
const { getMemberById } = useMember();
// derived values
const userDetails = getMemberById(id);
const profileLink = `/${workspaceSlug}/profile/${id}`;
if (!userDetails) {
return (
<div className="not-prose inline px-1 py-0.5 rounded bg-custom-background-80 text-custom-text-300 no-underline">
@deactivated user
</div>
);
}
return (
<div
className={cn("not-prose inline px-1 py-0.5 rounded bg-yellow-500/20 text-yellow-500 no-underline", {
"bg-custom-primary-100/20 text-custom-primary-100": id === currentUser?.id,
})}
>
<Link href={profileLink}>@{userDetails?.member__display_name}</Link>
</div>
);
});

View file

@ -1,3 +1,4 @@
export * from "./embeds";
export * from "./lite-text-editor";
export * from "./lite-text-read-only-editor";
export * from "./rich-text-read-only-editor";

View file

@ -2,13 +2,11 @@ import React from "react";
// editor
import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef } from "@plane/editor";
// components
import { IssueCommentToolbar } from "@/components/editor";
import { EditorMentionsRoot, IssueCommentToolbar } from "@/components/editor";
// helpers
import { cn } from "@/helpers/common.helper";
import { getEditorFileHandlers } from "@/helpers/editor.helper";
import { isCommentEmpty } from "@/helpers/string.helper";
// hooks
import { useMention } from "@/hooks/use-mention";
interface LiteTextEditorWrapperProps
extends Omit<ILiteTextEditor, "disabledExtensions" | "fileHandler" | "mentionHandler"> {
@ -29,8 +27,6 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
uploadFile,
...rest
} = props;
// use-mention
const { mentionHighlights } = useMention();
function isMutableRefObject<T>(ref: React.ForwardedRef<T>): ref is React.MutableRefObject<T | null> {
return !!ref && typeof ref === "object" && "current" in ref;
}
@ -49,8 +45,7 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
workspaceId,
})}
mentionHandler={{
highlights: mentionHighlights,
// suggestions disabled for now
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}
{...rest}
// overriding the containerClassName to add relative class passed

View file

@ -1,11 +1,11 @@
import React from "react";
// editor
import { EditorReadOnlyRefApi, ILiteTextReadOnlyEditor, LiteTextReadOnlyEditorWithRef } from "@plane/editor";
// components
import { EditorMentionsRoot } from "@/components/editor";
// helpers
import { cn } from "@/helpers/common.helper";
import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
// hooks
import { useMention } from "@/hooks/use-mention";
type LiteTextReadOnlyEditorWrapperProps = Omit<
ILiteTextReadOnlyEditor,
@ -15,25 +15,21 @@ type LiteTextReadOnlyEditorWrapperProps = Omit<
};
export const LiteTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, LiteTextReadOnlyEditorWrapperProps>(
({ anchor, ...props }, ref) => {
const { mentionHighlights } = useMention();
return (
<LiteTextReadOnlyEditorWithRef
ref={ref}
disabledExtensions={[]}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
})}
mentionHandler={{
highlights: mentionHighlights,
}}
{...props}
// overriding the customClassName to add relative class passed
containerClassName={cn(props.containerClassName, "relative p-2")}
/>
);
}
({ anchor, ...props }, ref) => (
<LiteTextReadOnlyEditorWithRef
ref={ref}
disabledExtensions={[]}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
})}
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}
{...props}
// overriding the customClassName to add relative class passed
containerClassName={cn(props.containerClassName, "relative p-2")}
/>
)
);
LiteTextReadOnlyEditor.displayName = "LiteTextReadOnlyEditor";

View file

@ -1,6 +1,8 @@
import React, { forwardRef } from "react";
// editor
import { EditorRefApi, IMentionHighlight, IRichTextEditor, RichTextEditorWithRef } from "@plane/editor";
import { EditorRefApi, IRichTextEditor, RichTextEditorWithRef } from "@plane/editor";
// components
import { EditorMentionsRoot } from "@/components/editor";
// helpers
import { getEditorFileHandlers } from "@/helpers/editor.helper";
@ -11,19 +13,11 @@ interface RichTextEditorWrapperProps
export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProps>((props, ref) => {
const { containerClassName, uploadFile, ...rest } = props;
// store hooks
// use-mention
// file size
return (
<RichTextEditorWithRef
mentionHandler={{
highlights: function (): Promise<IMentionHighlight[]> {
throw new Error("Function not implemented.");
},
suggestions: undefined,
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}
ref={ref}
disabledExtensions={[]}

View file

@ -1,11 +1,11 @@
import React from "react";
// editor
import { EditorReadOnlyRefApi, IRichTextReadOnlyEditor, RichTextReadOnlyEditorWithRef } from "@plane/editor";
// components
import { EditorMentionsRoot } from "@/components/editor";
// helpers
import { cn } from "@/helpers/common.helper";
import { getReadOnlyEditorFileHandlers } from "@/helpers/editor.helper";
// hooks
import { useMention } from "@/hooks/use-mention";
type RichTextReadOnlyEditorWrapperProps = Omit<
IRichTextReadOnlyEditor,
@ -15,23 +15,21 @@ type RichTextReadOnlyEditorWrapperProps = Omit<
};
export const RichTextReadOnlyEditor = React.forwardRef<EditorReadOnlyRefApi, RichTextReadOnlyEditorWrapperProps>(
({ anchor, ...props }, ref) => {
const { mentionHighlights } = useMention();
return (
<RichTextReadOnlyEditorWithRef
ref={ref}
disabledExtensions={[]}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
})}
mentionHandler={{ highlights: mentionHighlights }}
{...props}
// overriding the customClassName to add relative class passed
containerClassName={cn("relative p-0 border-none", props.containerClassName)}
/>
);
}
({ anchor, ...props }, ref) => (
<RichTextReadOnlyEditorWithRef
ref={ref}
disabledExtensions={[]}
fileHandler={getReadOnlyEditorFileHandlers({
anchor,
})}
mentionHandler={{
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}
{...props}
// overriding the customClassName to add relative class passed
containerClassName={cn("relative p-0 border-none", props.containerClassName)}
/>
)
);
RichTextReadOnlyEditor.displayName = "RichTextReadOnlyEditor";

View file

@ -1,45 +0,0 @@
import { computed, makeObservable } from "mobx";
// editor
import { IMentionHighlight } from "@plane/editor";
// store
import { CoreRootStore } from "@/store/root.store";
export interface IMentionsStore {
// mentionSuggestions: IMentionSuggestion[];
mentionHighlights: IMentionHighlight[];
}
export class MentionsStore implements IMentionsStore {
// root store
rootStore;
constructor(_rootStore: CoreRootStore) {
// rootStore
this.rootStore = _rootStore;
makeObservable(this, {
mentionHighlights: computed,
// mentionSuggestions: computed
});
}
// get mentionSuggestions() {
// const projectMembers = this.rootStore.project.project.
// const suggestions = projectMembers === null ? [] : projectMembers.map((member) => ({
// id: member.member.id,
// type: "User",
// title: member.member.display_name,
// subtitle: member.member.email ?? "",
// avatar: member.member.avatar,
// redirect_uri: `/${member.workspace.slug}/profile/${member.member.id}`,
// }))
// return suggestions
// }
get mentionHighlights() {
const user = this.rootStore.user.data;
return user ? [user.id] : [];
}
}

View file

@ -8,7 +8,6 @@ import { CycleStore, ICycleStore } from "./cycle.store";
import { IssueFilterStore, IIssueFilterStore } from "./issue-filters.store";
import { IIssueLabelStore, LabelStore } from "./label.store";
import { IIssueMemberStore, MemberStore } from "./members.store";
import { IMentionsStore, MentionsStore } from "./mentions.store";
import { IIssueModuleStore, ModuleStore } from "./module.store";
import { IPublishListStore, PublishListStore } from "./publish/publish_list.store";
import { IStateStore, StateStore } from "./state.store";
@ -20,7 +19,6 @@ export class CoreRootStore {
user: IUserStore;
issue: IIssueStore;
issueDetail: IIssueDetailStore;
mentionStore: IMentionsStore;
state: IStateStore;
label: IIssueLabelStore;
module: IIssueModuleStore;
@ -34,7 +32,6 @@ export class CoreRootStore {
this.user = new UserStore(this);
this.issue = new IssueStore(this);
this.issueDetail = new IssueDetailStore(this);
this.mentionStore = new MentionsStore(this);
this.state = new StateStore(this);
this.label = new LabelStore(this);
this.module = new ModuleStore(this);
@ -57,7 +54,6 @@ export class CoreRootStore {
this.user = new UserStore(this);
this.issue = new IssueStore(this);
this.issueDetail = new IssueDetailStore(this);
this.mentionStore = new MentionsStore(this);
this.state = new StateStore(this);
this.label = new LabelStore(this);
this.module = new ModuleStore(this);