[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:
parent
c10b875e2a
commit
119d343d5f
78 changed files with 1491 additions and 992 deletions
1
space/core/components/editor/embeds/index.ts
Normal file
1
space/core/components/editor/embeds/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./mentions";
|
||||
1
space/core/components/editor/embeds/mentions/index.ts
Normal file
1
space/core/components/editor/embeds/mentions/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./root";
|
||||
17
space/core/components/editor/embeds/mentions/root.tsx
Normal file
17
space/core/components/editor/embeds/mentions/root.tsx
Normal 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} />;
|
||||
}
|
||||
};
|
||||
41
space/core/components/editor/embeds/mentions/user.tsx
Normal file
41
space/core/components/editor/embeds/mentions/user.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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={[]}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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] : [];
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue