[WIKI-773] refactor: editor mention components and hooks (#8111)
This commit is contained in:
parent
c04ae51d20
commit
c65e2c4aab
17 changed files with 84 additions and 96 deletions
|
|
@ -1,4 +1,4 @@
|
|||
// plane editor
|
||||
import type { TMentionComponentProps } from "@plane/editor";
|
||||
import type { TCallbackMentionComponentProps } from "@plane/editor";
|
||||
|
||||
export const EditorAdditionalMentionsRoot: React.FC<TMentionComponentProps> = () => null;
|
||||
export const EditorAdditionalMentionsRoot: React.FC<TCallbackMentionComponentProps> = () => null;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// plane editor
|
||||
import type { TMentionComponentProps } from "@plane/editor";
|
||||
import type { TCallbackMentionComponentProps } 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) => {
|
||||
export const EditorMentionsRoot: React.FC<TCallbackMentionComponentProps> = (props) => {
|
||||
const { entity_identifier, entity_name } = props;
|
||||
|
||||
switch (entity_name) {
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export * from "./mentions";
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
// plane editor
|
||||
import type { TMentionComponentProps } from "@plane/editor";
|
||||
// plane imports
|
||||
import type { TCallbackMentionComponentProps } from "@plane/editor";
|
||||
|
||||
export const EditorAdditionalMentionsRoot: React.FC<TMentionComponentProps> = () => null;
|
||||
export type TEditorMentionComponentProps = TCallbackMentionComponentProps;
|
||||
|
||||
export const EditorAdditionalMentionsRoot: React.FC<TEditorMentionComponentProps> = () => null;
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export * from "./embeds";
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
// plane types
|
||||
import type { TSearchEntities } from "@plane/types";
|
||||
|
||||
export const EDITOR_MENTION_TYPES: TSearchEntities[] = ["user_mention"];
|
||||
|
|
@ -1,11 +1,18 @@
|
|||
import { useCallback } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
// plane editor
|
||||
import type { TMentionSection } from "@plane/editor";
|
||||
// plane types
|
||||
import type { TSearchEntities, TSearchResponse } from "@plane/types";
|
||||
|
||||
export type TUseAdditionalEditorMentionArgs = {
|
||||
enableAdvancedMentions: boolean;
|
||||
};
|
||||
|
||||
export type TAdditionalEditorMentionHandlerArgs = {
|
||||
response: TSearchResponse;
|
||||
};
|
||||
|
||||
export type TAdditionalEditorMentionHandlerReturnType = {
|
||||
sections: TMentionSection[];
|
||||
};
|
||||
|
||||
|
|
@ -21,21 +28,24 @@ export type TAdditionalParseEditorContentReturnType =
|
|||
}
|
||||
| undefined;
|
||||
|
||||
export const useAdditionalEditorMention = () => {
|
||||
const updateAdditionalSections = useCallback((args: TAdditionalEditorMentionHandlerArgs) => {
|
||||
const {} = args;
|
||||
}, []);
|
||||
|
||||
const parseAdditionalEditorContent = useCallback(
|
||||
(args: TAdditionalParseEditorContentArgs): TAdditionalParseEditorContentReturnType => {
|
||||
const {} = args;
|
||||
return undefined;
|
||||
},
|
||||
export const useAdditionalEditorMention = (_args: TUseAdditionalEditorMentionArgs) => {
|
||||
const updateAdditionalSections = useCallback(
|
||||
(_args: TAdditionalEditorMentionHandlerArgs): TAdditionalEditorMentionHandlerReturnType => ({
|
||||
sections: [],
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const parseAdditionalEditorContent = useCallback(
|
||||
(_args: TAdditionalParseEditorContentArgs): TAdditionalParseEditorContentReturnType => undefined,
|
||||
[]
|
||||
);
|
||||
|
||||
const editorMentionTypes: TSearchEntities[] = useMemo(() => ["user_mention"], []);
|
||||
|
||||
return {
|
||||
updateAdditionalSections,
|
||||
parseAdditionalEditorContent,
|
||||
editorMentionTypes,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import { forwardRef } from "react";
|
||||
// plane imports
|
||||
import { DocumentEditorWithRef } from "@plane/editor";
|
||||
import type { IEditorPropsExtended, EditorRefApi, IDocumentEditorProps, TFileHandler } from "@plane/editor";
|
||||
|
|
@ -50,6 +50,7 @@ export const DocumentEditor = forwardRef<EditorRefApi, DocumentEditorWrapperProp
|
|||
});
|
||||
// use editor mention
|
||||
const { fetchMentions } = useEditorMention({
|
||||
enableAdvancedMentions: true,
|
||||
searchEntity: editable ? async (payload) => await props.searchMentionCallback(payload) : async () => ({}),
|
||||
});
|
||||
// editor config
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
// plane editor
|
||||
import type { TMentionComponentProps } from "@plane/editor";
|
||||
// plane web components
|
||||
import { EditorAdditionalMentionsRoot } from "@/plane-web/components/editor";
|
||||
// local components
|
||||
// plane web imports
|
||||
import type { TEditorMentionComponentProps } from "@/plane-web/components/editor/embeds/mentions";
|
||||
import { EditorAdditionalMentionsRoot } from "@/plane-web/components/editor/embeds/mentions";
|
||||
// local imports
|
||||
import { EditorUserMention } from "./user";
|
||||
|
||||
export const EditorMentionsRoot: React.FC<TMentionComponentProps> = (props) => {
|
||||
export const EditorMentionsRoot: React.FC<TEditorMentionComponentProps> = (props) => {
|
||||
const { entity_identifier, entity_name } = props;
|
||||
|
||||
switch (entity_name) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Link } from "react-router";
|
||||
// plane imports
|
||||
import { ROLE } from "@plane/constants";
|
||||
// plane ui
|
||||
import { Popover } from "@plane/propel/popover";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { cn, getFileURL } from "@plane/utils";
|
||||
// constants
|
||||
// helpers
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store/use-member";
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
|
|
@ -22,9 +18,6 @@ export const EditorUserMention: React.FC<Props> = observer((props) => {
|
|||
const { id } = props;
|
||||
// router
|
||||
const { projectId } = useParams();
|
||||
// states
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLAnchorElement | null>(null);
|
||||
// params
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
|
|
@ -33,18 +26,6 @@ export const EditorUserMention: React.FC<Props> = observer((props) => {
|
|||
getUserDetails,
|
||||
project: { getProjectMemberDetails },
|
||||
} = useMember();
|
||||
// popper-js refs
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
padding: 12,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
// derived values
|
||||
const userDetails = getUserDetails(id);
|
||||
const roleDetails = projectId ? getProjectMemberDetails(id, projectId.toString())?.role : null;
|
||||
|
|
@ -53,7 +34,7 @@ export const EditorUserMention: React.FC<Props> = observer((props) => {
|
|||
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
|
||||
@suspended user
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -61,21 +42,18 @@ export const EditorUserMention: React.FC<Props> = observer((props) => {
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"not-prose group/user-mention inline px-1 py-0.5 rounded bg-custom-primary-100/20 text-custom-primary-100 no-underline",
|
||||
"not-prose inline px-1 py-0.5 rounded bg-custom-primary-100/20 text-custom-primary-100 no-underline",
|
||||
{
|
||||
"bg-yellow-500/20 text-yellow-500": id === currentUser?.id,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Link href={profileLink} ref={setReferenceElement}>
|
||||
@{userDetails?.display_name}
|
||||
</Link>
|
||||
<div
|
||||
className="top-full left-0 z-10 min-w-60 bg-custom-background-90 shadow-custom-shadow-rg rounded-lg p-4 opacity-0 pointer-events-none group-hover/user-mention:opacity-100 group-hover/user-mention:pointer-events-auto transition-opacity"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<Popover delay={100} openOnHover>
|
||||
<Popover.Button>
|
||||
<Link to={profileLink}>@{userDetails?.display_name}</Link>
|
||||
</Popover.Button>
|
||||
<Popover.Panel side="bottom" align="start">
|
||||
<div className="w-60 bg-custom-background-100 shadow-custom-shadow-rg rounded-lg p-3 border-[0.5px] border-custom-border-300">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-shrink-0 size-10 grid place-items-center">
|
||||
<Avatar
|
||||
|
|
@ -87,13 +65,15 @@ export const EditorUserMention: React.FC<Props> = observer((props) => {
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Link href={profileLink} className="not-prose font-medium text-custom-text-100 text-sm hover:underline">
|
||||
<Link to={profileLink} className="not-prose font-medium text-custom-text-100 text-sm hover:underline">
|
||||
{userDetails?.first_name} {userDetails?.last_name}
|
||||
</Link>
|
||||
{roleDetails && <p className="text-custom-text-200 text-xs">{ROLE[roleDetails]}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ export const PageEditorBody: React.FC<Props> = observer((props) => {
|
|||
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id ?? "";
|
||||
// use editor mention
|
||||
const { fetchMentions } = useEditorMention({
|
||||
enableAdvancedMentions: true,
|
||||
searchEntity: handlers.fetchEntity,
|
||||
});
|
||||
// editor flaggings
|
||||
|
|
|
|||
|
|
@ -7,26 +7,27 @@ import type { TSearchEntities, TSearchEntityRequestPayload, TSearchResponse, TUs
|
|||
import { Avatar } from "@plane/ui";
|
||||
// helpers
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// plane web constants
|
||||
import { EDITOR_MENTION_TYPES } from "@/plane-web/constants/editor";
|
||||
// plane web hooks
|
||||
import { useAdditionalEditorMention } from "@/plane-web/hooks/use-additional-editor-mention";
|
||||
|
||||
type TArgs = {
|
||||
enableAdvancedMentions?: boolean;
|
||||
searchEntity: (payload: TSearchEntityRequestPayload) => Promise<TSearchResponse>;
|
||||
};
|
||||
|
||||
export const useEditorMention = (args: TArgs) => {
|
||||
const { searchEntity } = args;
|
||||
const { enableAdvancedMentions = false, searchEntity } = args;
|
||||
// additional mentions
|
||||
const { updateAdditionalSections } = useAdditionalEditorMention();
|
||||
const { editorMentionTypes, updateAdditionalSections } = useAdditionalEditorMention({
|
||||
enableAdvancedMentions,
|
||||
});
|
||||
// fetch mentions handler
|
||||
const fetchMentions = useCallback(
|
||||
async (query: string): Promise<TMentionSection[]> => {
|
||||
try {
|
||||
const res = await searchEntity({
|
||||
count: 5,
|
||||
query_type: EDITOR_MENTION_TYPES,
|
||||
query_type: editorMentionTypes,
|
||||
query,
|
||||
});
|
||||
const suggestionSections: TMentionSection[] = [];
|
||||
|
|
@ -57,17 +58,16 @@ export const useEditorMention = (args: TArgs) => {
|
|||
});
|
||||
}
|
||||
});
|
||||
updateAdditionalSections({
|
||||
const { sections } = updateAdditionalSections({
|
||||
response: res,
|
||||
sections: suggestionSections,
|
||||
});
|
||||
return suggestionSections;
|
||||
return [...suggestionSections, ...sections];
|
||||
} catch (error) {
|
||||
console.error("Error in fetching mentions for project pages:", error);
|
||||
console.error("Error in fetching mentions:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[searchEntity, updateAdditionalSections]
|
||||
[editorMentionTypes, searchEntity, updateAdditionalSections]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ export const useParseEditorContent = () => {
|
|||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
// parse additional content
|
||||
const { parseAdditionalEditorContent } = useAdditionalEditorMention();
|
||||
const { parseAdditionalEditorContent } = useAdditionalEditorMention({
|
||||
enableAdvancedMentions: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description function to replace all the custom components from the html component to make it pdf compatible
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export * from "ce/components/editor";
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from "ce/constants/editor";
|
||||
|
|
@ -16,10 +16,10 @@ export type TMentionSection = {
|
|||
items: TMentionSuggestion[];
|
||||
};
|
||||
|
||||
export type TMentionComponentProps = Pick<TMentionSuggestion, "entity_identifier" | "entity_name">;
|
||||
export type TCallbackMentionComponentProps = Pick<TMentionSuggestion, "entity_identifier" | "entity_name">;
|
||||
|
||||
export type TMentionHandler = {
|
||||
getMentionedEntityDetails?: (entity_identifier: string) => { display_name: string } | undefined;
|
||||
renderComponent: (props: TMentionComponentProps) => React.ReactNode;
|
||||
renderComponent: (props: TCallbackMentionComponentProps) => React.ReactNode;
|
||||
searchCallback?: (query: string) => Promise<TMentionSection[]>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { TIssuePriorities } from "../issues";
|
||||
import type { TStateGroups } from "../state";
|
||||
import type { TIssuePublicComment } from "./activity/issue_comment";
|
||||
import type { TIssueAttachment } from "./issue_attachment";
|
||||
import type { TIssueLink } from "./issue_link";
|
||||
|
|
@ -93,7 +94,7 @@ export type TIssue = TBaseIssue & {
|
|||
tempId?: string;
|
||||
// sourceIssueId is used to store the original issue id when creating a copy of an issue. Used in cloning property values. It is not a part of the API response.
|
||||
sourceIssueId?: string;
|
||||
state__group?: string | null;
|
||||
state__group?: TStateGroups | null;
|
||||
};
|
||||
|
||||
export type TIssueMap = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue