fixed mentions not showing in modals

This commit is contained in:
Palanikannan1437 2024-03-04 13:20:44 +05:30
parent a3033203af
commit 43fd94827f
22 changed files with 253 additions and 167 deletions

View File

@ -5,7 +5,7 @@ import { CoreEditorExtensions } from "src/ui/extensions";
import { EditorProps } from "@tiptap/pm/view"; import { EditorProps } from "@tiptap/pm/view";
import { getTrimmedHTML } from "src/lib/utils"; import { getTrimmedHTML } from "src/lib/utils";
import { DeleteImage } from "src/types/delete-image"; import { DeleteImage } from "src/types/delete-image";
import { IMentionSuggestion } from "src/types/mention-suggestion"; import { IMentionHighlight, IMentionSuggestion } from "src/types/mention-suggestion";
import { RestoreImage } from "src/types/restore-image"; import { RestoreImage } from "src/types/restore-image";
import { UploadImage } from "src/types/upload-image"; import { UploadImage } from "src/types/upload-image";
@ -27,8 +27,8 @@ interface CustomEditorProps {
extensions?: any; extensions?: any;
editorProps?: EditorProps; editorProps?: EditorProps;
forwardedRef?: any; forwardedRef?: any;
mentionHighlights?: string[]; mentionHighlights?: () => Promise<IMentionHighlight[]>;
mentionSuggestions?: IMentionSuggestion[]; mentionSuggestions?: () => Promise<IMentionSuggestion[]>;
} }
export const useEditor = ({ export const useEditor = ({
@ -48,6 +48,7 @@ export const useEditor = ({
mentionHighlights, mentionHighlights,
mentionSuggestions, mentionSuggestions,
}: CustomEditorProps) => { }: CustomEditorProps) => {
console.log("the mentions", mentionHighlights);
const editor = useCustomEditor( const editor = useCustomEditor(
{ {
editorProps: { editorProps: {
@ -57,8 +58,8 @@ export const useEditor = ({
extensions: [ extensions: [
...CoreEditorExtensions( ...CoreEditorExtensions(
{ {
mentionSuggestions: mentionSuggestions ?? [], mentionSuggestions: mentionSuggestions,
mentionHighlights: mentionHighlights ?? [], mentionHighlights: mentionHighlights,
}, },
deleteFile, deleteFile,
restoreFile, restoreFile,

View File

@ -68,7 +68,7 @@
top: 0; top: 0;
bottom: -2px; bottom: -2px;
width: 4px; width: 4px;
z-index: 99; z-index: 5;
background-color: rgba(var(--color-primary-400)); background-color: rgba(var(--color-primary-400));
pointer-events: none; pointer-events: none;
} }
@ -81,7 +81,7 @@
.tableWrapper .tableControls .rowsControl { .tableWrapper .tableControls .rowsControl {
transition: opacity ease-in 100ms; transition: opacity ease-in 100ms;
position: absolute; position: absolute;
z-index: 99; z-index: 5;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@ -22,94 +22,92 @@ import { CustomKeymap } from "src/ui/extensions/keymap";
import { CustomQuoteExtension } from "src/ui/extensions/quote"; import { CustomQuoteExtension } from "src/ui/extensions/quote";
import { DeleteImage } from "src/types/delete-image"; import { DeleteImage } from "src/types/delete-image";
import { IMentionSuggestion } from "src/types/mention-suggestion"; import { IMentionHighlight, IMentionSuggestion } from "src/types/mention-suggestion";
import { RestoreImage } from "src/types/restore-image"; import { RestoreImage } from "src/types/restore-image";
import { CustomLinkExtension } from "src/ui/extensions/custom-link"; import { CustomLinkExtension } from "src/ui/extensions/custom-link";
import { CustomCodeInlineExtension } from "./code-inline"; import { CustomCodeInlineExtension } from "./code-inline";
export const CoreEditorExtensions = ( export const CoreEditorExtensions = (
mentionConfig: { mentionConfig: {
mentionSuggestions: IMentionSuggestion[]; mentionSuggestions?: () => Promise<IMentionSuggestion[]>;
mentionHighlights: string[]; mentionHighlights?: () => Promise<IMentionHighlight[]>;
}, },
deleteFile: DeleteImage, deleteFile: DeleteImage,
restoreFile: RestoreImage, restoreFile: RestoreImage,
cancelUploadImage?: () => any cancelUploadImage?: () => any
) => { ) => [
return [ StarterKit.configure({
StarterKit.configure({ bulletList: {
bulletList: {
HTMLAttributes: {
class: "list-disc list-outside leading-3 -mt-2",
},
},
orderedList: {
HTMLAttributes: {
class: "list-decimal list-outside leading-3 -mt-2",
},
},
listItem: {
HTMLAttributes: {
class: "leading-normal -mb-2",
},
},
code: false,
codeBlock: false,
horizontalRule: {
HTMLAttributes: { class: "mt-4 mb-4" },
},
blockquote: false,
dropcursor: {
color: "rgba(var(--color-text-100))",
width: 2,
},
}),
CustomQuoteExtension.configure({
HTMLAttributes: { className: "border-l-4 border-custom-border-300" },
}),
CustomKeymap,
ListKeymap,
CustomLinkExtension.configure({
openOnClick: true,
autolink: true,
linkOnPaste: true,
protocols: ["http", "https"],
validate: (url: string) => isValidHttpUrl(url),
HTMLAttributes: { HTMLAttributes: {
class: class: "list-disc list-outside leading-3 -mt-2",
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
}, },
}), },
ImageExtension(deleteFile, restoreFile, cancelUploadImage).configure({ orderedList: {
HTMLAttributes: { HTMLAttributes: {
class: "rounded-lg border border-custom-border-300", class: "list-decimal list-outside leading-3 -mt-2",
}, },
}), },
TiptapUnderline, listItem: {
TextStyle,
Color,
TaskList.configure({
HTMLAttributes: { HTMLAttributes: {
class: "not-prose pl-2", class: "leading-normal -mb-2",
}, },
}), },
TaskItem.configure({ code: false,
HTMLAttributes: { codeBlock: false,
class: "flex items-start my-4", horizontalRule: {
}, HTMLAttributes: { class: "mt-4 mb-4" },
nested: true, },
}), blockquote: false,
CustomCodeBlockExtension, dropcursor: {
CustomCodeInlineExtension, color: "rgba(var(--color-text-100))",
Markdown.configure({ width: 2,
html: true, },
transformCopiedText: true, }),
transformPastedText: true, CustomQuoteExtension.configure({
}), HTMLAttributes: { className: "border-l-4 border-custom-border-300" },
Table, }),
TableHeader, CustomKeymap,
TableCell, ListKeymap,
TableRow, CustomLinkExtension.configure({
Mentions(mentionConfig.mentionSuggestions, mentionConfig.mentionHighlights, false), openOnClick: true,
]; autolink: true,
}; linkOnPaste: true,
protocols: ["http", "https"],
validate: (url: string) => isValidHttpUrl(url),
HTMLAttributes: {
class:
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
},
}),
ImageExtension(deleteFile, restoreFile, cancelUploadImage).configure({
HTMLAttributes: {
class: "rounded-lg border border-custom-border-300",
},
}),
TiptapUnderline,
TextStyle,
Color,
TaskList.configure({
HTMLAttributes: {
class: "not-prose pl-2",
},
}),
TaskItem.configure({
HTMLAttributes: {
class: "flex items-start my-4",
},
nested: true,
}),
CustomCodeBlockExtension,
CustomCodeInlineExtension,
Markdown.configure({
html: true,
transformCopiedText: true,
transformPastedText: true,
}),
Table,
TableHeader,
TableCell,
TableRow,
Mentions(mentionConfig.mentionSuggestions, mentionConfig.mentionHighlights, false),
];

View File

@ -5,7 +5,7 @@ import { MentionNodeView } from "src/ui/mentions/mention-node-view";
import { IMentionHighlight } from "src/types/mention-suggestion"; import { IMentionHighlight } from "src/types/mention-suggestion";
export interface CustomMentionOptions extends MentionOptions { export interface CustomMentionOptions extends MentionOptions {
mentionHighlights: IMentionHighlight[]; mentionHighlights: () => Promise<IMentionHighlight[]>;
readonly?: boolean; readonly?: boolean;
} }

View File

@ -7,29 +7,9 @@ import tippy from "tippy.js";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { MentionList } from "src/ui/mentions/mention-list"; import { MentionList } from "src/ui/mentions/mention-list";
export const getSuggestionItems =
(getSuggestions: () => Promise<IMentionSuggestion[]>) =>
async ({ query }: { query: string }) => {
console.log("yaa");
const suggestions = await getSuggestions();
const mappedSuggestions: IMentionSuggestion[] = suggestions.map((suggestion): IMentionSuggestion => {
const transactionId = uuidv4();
return {
...suggestion,
id: transactionId,
};
});
const filteredSuggestions = mappedSuggestions
.filter((suggestion) => suggestion.title.toLowerCase().startsWith(query.toLowerCase()))
.slice(0, 5);
console.log("yoo", filteredSuggestions);
return filteredSuggestions;
};
export const Mentions = ( export const Mentions = (
mentionSuggestions: () => Promise<IMentionSuggestion[]>, mentionSuggestions: () => Promise<IMentionSuggestion[]>,
mentionHighlights: IMentionHighlight[], mentionHighlights: () => Promise<IMentionHighlight[]>,
readonly: boolean readonly: boolean
) => ) =>
CustomMention.configure({ CustomMention.configure({
@ -39,8 +19,8 @@ export const Mentions = (
readonly: readonly, readonly: readonly,
mentionHighlights: mentionHighlights, mentionHighlights: mentionHighlights,
suggestion: { suggestion: {
items: ({ query }) => { items: async ({ query }) => {
const suggestions = mentionSuggestions(); const suggestions = await mentionSuggestions();
const mappedSuggestions: IMentionSuggestion[] = suggestions.map((suggestion): IMentionSuggestion => { const mappedSuggestions: IMentionSuggestion[] = suggestions.map((suggestion): IMentionSuggestion => {
const transactionId = uuidv4(); const transactionId = uuidv4();
return { return {
@ -48,11 +28,11 @@ export const Mentions = (
id: transactionId, id: transactionId,
}; };
}); });
const filteredSuggestions = mappedSuggestions const filteredSuggestions = mappedSuggestions
.filter((suggestion) => suggestion.title.toLowerCase().startsWith(query.toLowerCase())) .filter((suggestion) => suggestion.title.toLowerCase().startsWith(query.toLowerCase()))
.slice(0, 5); .slice(0, 5);
console.log("yoo", filteredSuggestions);
return filteredSuggestions; return filteredSuggestions;
}, },
// @ts-ignore // @ts-ignore
@ -60,33 +40,44 @@ export const Mentions = (
let reactRenderer: ReactRenderer | null = null; let reactRenderer: ReactRenderer | null = null;
let popup: any | null = null; let popup: any | null = null;
const hidePopup = () => {
popup?.[0].hide();
};
return { return {
onStart: (props: { editor: Editor; clientRect: DOMRect }) => { onStart: (props: { editor: Editor; clientRect: DOMRect }) => {
props.editor.storage.mentionsOpen = true; if (!props.clientRect) {
return;
}
reactRenderer = new ReactRenderer(MentionList, { reactRenderer = new ReactRenderer(MentionList, {
props, props,
editor: props.editor, editor: props.editor,
}); });
props.editor.storage.mentionsOpen = true;
// @ts-ignore // @ts-ignore
popup = tippy("body", { popup = tippy("body", {
getReferenceClientRect: props.clientRect, getReferenceClientRect: props.clientRect,
appendTo: () => document.querySelector("#editor-container"), appendTo: () => document.body,
content: reactRenderer.element, content: reactRenderer.element,
showOnCreate: true, showOnCreate: true,
interactive: true, interactive: true,
trigger: "manual", trigger: "manual",
placement: "bottom-start", placement: "bottom-start",
}); });
// document.addEventListener("scroll", hidePopup, true);
}, },
onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => { onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => {
reactRenderer?.updateProps(props); reactRenderer?.updateProps(props);
if (!props.clientRect) {
return;
}
popup && popup &&
popup[0].setProps({ popup[0].setProps({
getReferenceClientRect: props.clientRect, getReferenceClientRect: props.clientRect,
}); });
}, },
onKeyDown: (props: { event: KeyboardEvent }) => { onKeyDown: (props: { event: KeyboardEvent }) => {
if (props.event.key === "Escape") { if (props.event.key === "Escape") {
popup?.[0].hide(); popup?.[0].hide();
@ -108,6 +99,8 @@ export const Mentions = (
props.editor.storage.mentionsOpen = false; props.editor.storage.mentionsOpen = false;
popup?.[0].destroy(); popup?.[0].destroy();
reactRenderer?.destroy(); reactRenderer?.destroy();
// document.removeEventListener("scroll", hidePopup, true);
}, },
}; };
}, },

View File

@ -12,6 +12,7 @@ interface MentionListProps {
export const MentionList = forwardRef((props: MentionListProps, ref) => { export const MentionList = forwardRef((props: MentionListProps, ref) => {
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
console.log("props", props);
const selectItem = (index: number) => { const selectItem = (index: number) => {
const item = props.items[index]; const item = props.items[index];

View File

@ -4,11 +4,22 @@ import { NodeViewWrapper } from "@tiptap/react";
import { cn } from "src/lib/utils"; import { cn } from "src/lib/utils";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { IMentionHighlight } from "src/types/mention-suggestion"; import { IMentionHighlight } from "src/types/mention-suggestion";
import { useEffect, useState } from "react";
// eslint-disable-next-line import/no-anonymous-default-export // eslint-disable-next-line import/no-anonymous-default-export
export const MentionNodeView = (props) => { export const MentionNodeView = (props) => {
const router = useRouter(); const router = useRouter();
const highlights = props.extension.options.mentionHighlights as IMentionHighlight[]; // const highlights = props.extension.options.mentionHighlights as IMentionHighlight[];
const [highlightsState, setHighlightsState] = useState();
useEffect(() => {
console.log("hightlights type", props.extension.options.mentionHighlights);
const hightlights = async () => {
const userId = await props.extension.options.mentionHighlights();
setHighlightsState(userId);
};
hightlights();
}, []);
const handleClick = () => { const handleClick = () => {
if (!props.extension.options.readonly) { if (!props.extension.options.readonly) {
@ -16,12 +27,13 @@ export const MentionNodeView = (props) => {
} }
}; };
console.log("state of highlight", highlightsState);
return ( return (
<NodeViewWrapper className="mention-component inline w-fit"> <NodeViewWrapper className="mention-component inline w-fit">
<span <span
className={cn("mention rounded bg-custom-primary-100/20 px-1 py-0.5 font-medium text-custom-primary-100", { className={cn("mention rounded bg-custom-primary-100/20 px-1 py-0.5 font-medium text-custom-primary-100", {
"bg-yellow-500/20 text-yellow-500": highlights "bg-yellow-500/20 text-yellow-500": highlightsState
? highlights.includes(props.node.attrs.entity_identifier) ? highlightsState.includes(props.node.attrs.entity_identifier)
: false, : false,
"cursor-pointer": !props.extension.options.readonly, "cursor-pointer": !props.extension.options.readonly,
// "hover:bg-custom-primary-300" : !props.extension.options.readonly && !highlights.includes(props.node.attrs.id) // "hover:bg-custom-primary-300" : !props.extension.options.readonly && !highlights.includes(props.node.attrs.id)

View File

@ -126,6 +126,7 @@ const DocumentEditor = ({
extensions: DocumentEditorExtensions(uploadFile, setHideDragHandleFunction, setIsSubmitting), extensions: DocumentEditorExtensions(uploadFile, setHideDragHandleFunction, setIsSubmitting),
}); });
console.log("in document editor", mentionHighlights);
if (!editor) { if (!editor) {
return null; return null;
} }

View File

@ -327,10 +327,10 @@ const renderItems = () => {
// @ts-ignore // @ts-ignore
popup = tippy("body", { popup = tippy("body", {
getReferenceClientRect: props.clientRect, getReferenceClientRect: props.clientRect,
appendTo: () => document.querySelector("#editor-container"), appendTo: () => document.body,
content: component.element, content: component.element,
showOnCreate: true, showOnCreate: true,
interactive: true, // interactive: true,
trigger: "manual", trigger: "manual",
placement: "bottom-start", placement: "bottom-start",
}); });

View File

@ -15,7 +15,6 @@ export interface IUser {
is_email_verified: boolean; is_email_verified: boolean;
is_managed: boolean; is_managed: boolean;
is_onboarded: boolean; is_onboarded: boolean;
is_password_autoset: boolean;
is_tour_completed: boolean; is_tour_completed: boolean;
is_password_autoset: boolean; is_password_autoset: boolean;
mobile_number: string | null; mobile_number: string | null;

View File

@ -48,7 +48,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
// toast alert // toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { mentionHighlights, mentionSuggestions } = useMention();
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, inboxId } = router.query as { const { workspaceSlug, projectId, inboxId } = router.query as {
@ -59,6 +59,13 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
const workspaceStore = useWorkspace(); const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string; const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
console.log("in create issue modal", workspaceSlug, projectId);
const { mentionHighlights, mentionSuggestions } = useMention({
workspaceSlug: workspaceSlug as string,
projectId: projectId as string,
});
// store hooks // store hooks
const { const {
issues: { createInboxIssue }, issues: { createInboxIssue },

View File

@ -48,7 +48,11 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = observer((props) => {
const { setShowAlert } = useReloadConfirmations(); const { setShowAlert } = useReloadConfirmations();
// store hooks // store hooks
const { mentionHighlights, mentionSuggestions } = useMention(); const { mentionHighlights, mentionSuggestions } = useMention({
workspaceSlug: workspaceSlug as string,
projectId: projectId as string,
});
// form info // form info
const { const {
handleSubmit, handleSubmit,

View File

@ -106,10 +106,6 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
// store hooks // store hooks
const { areEstimatesEnabledForProject } = useEstimate(); const { areEstimatesEnabledForProject } = useEstimate();
const { mentionHighlights, mentionSuggestions } = useMention();
// hooks
const { setValue: setLocalStorageValue } = useLocalStorage("draftedIssue", {});
const { setToastAlert } = useToast();
// refs // refs
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
// router // router
@ -118,6 +114,14 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
const workspaceStore = useWorkspace(); const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string; const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
// hooks
const { setValue: setLocalStorageValue } = useLocalStorage("draftedIssue", {});
const { setToastAlert } = useToast();
const { mentionHighlights, mentionSuggestions } = useMention({
workspaceSlug: workspaceSlug as string,
projectId: projectId as string,
});
// store // store
const { const {
config: { envConfig }, config: { envConfig },

View File

@ -10,13 +10,14 @@ import { TActivityOperations } from "./root";
type TIssueActivityCommentRoot = { type TIssueActivityCommentRoot = {
workspaceSlug: string; workspaceSlug: string;
projectId: string;
issueId: string; issueId: string;
activityOperations: TActivityOperations; activityOperations: TActivityOperations;
showAccessSpecifier?: boolean; showAccessSpecifier?: boolean;
}; };
export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer((props) => { export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer((props) => {
const { workspaceSlug, issueId, activityOperations, showAccessSpecifier } = props; const { workspaceSlug, issueId, activityOperations, showAccessSpecifier, projectId } = props;
// hooks // hooks
const { const {
activity: { getActivityCommentByIssueId }, activity: { getActivityCommentByIssueId },
@ -31,6 +32,7 @@ export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer(
{activityComments.map((activityComment, index) => {activityComments.map((activityComment, index) =>
activityComment.activity_type === "COMMENT" ? ( activityComment.activity_type === "COMMENT" ? (
<IssueCommentCard <IssueCommentCard
projectId={projectId}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
commentId={activityComment.id} commentId={activityComment.id}
activityOperations={activityOperations} activityOperations={activityOperations}

View File

@ -18,6 +18,7 @@ import { TActivityOperations } from "../root";
const fileService = new FileService(); const fileService = new FileService();
type TIssueCommentCard = { type TIssueCommentCard = {
projectId: string;
workspaceSlug: string; workspaceSlug: string;
commentId: string; commentId: string;
activityOperations: TActivityOperations; activityOperations: TActivityOperations;
@ -26,13 +27,16 @@ type TIssueCommentCard = {
}; };
export const IssueCommentCard: FC<TIssueCommentCard> = (props) => { export const IssueCommentCard: FC<TIssueCommentCard> = (props) => {
const { workspaceSlug, commentId, activityOperations, ends, showAccessSpecifier = false } = props; const { workspaceSlug, projectId, commentId, activityOperations, ends, showAccessSpecifier = false } = props;
// hooks // hooks
const { const {
comment: { getCommentById }, comment: { getCommentById },
} = useIssueDetail(); } = useIssueDetail();
const { currentUser } = useUser(); const { currentUser } = useUser();
const { mentionHighlights, mentionSuggestions } = useMention(); const { mentionHighlights, mentionSuggestions } = useMention({
workspaceSlug: workspaceSlug as string,
projectId: projectId as string,
});
// refs // refs
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
const showEditorRef = useRef<any>(null); const showEditorRef = useRef<any>(null);

View File

@ -15,6 +15,7 @@ import { useMention, useWorkspace } from "hooks/store";
const fileService = new FileService(); const fileService = new FileService();
type TIssueCommentCreate = { type TIssueCommentCreate = {
projectId: string;
workspaceSlug: string; workspaceSlug: string;
activityOperations: TActivityOperations; activityOperations: TActivityOperations;
showAccessSpecifier?: boolean; showAccessSpecifier?: boolean;
@ -39,11 +40,14 @@ const commentAccess: commentAccessType[] = [
]; ];
export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => { export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
const { workspaceSlug, activityOperations, showAccessSpecifier = false } = props; const { workspaceSlug, projectId, activityOperations, showAccessSpecifier = false } = props;
const workspaceStore = useWorkspace(); const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string; const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
const { mentionHighlights, mentionSuggestions } = useMention(); const { mentionHighlights, mentionSuggestions } = useMention({
workspaceSlug: workspaceSlug as string,
projectId: projectId as string,
});
// refs // refs
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);

View File

@ -8,6 +8,7 @@ import { IssueCommentCard } from "./comment-card";
import { TActivityOperations } from "../root"; import { TActivityOperations } from "../root";
type TIssueCommentRoot = { type TIssueCommentRoot = {
projectId: string;
workspaceSlug: string; workspaceSlug: string;
issueId: string; issueId: string;
activityOperations: TActivityOperations; activityOperations: TActivityOperations;
@ -15,7 +16,7 @@ type TIssueCommentRoot = {
}; };
export const IssueCommentRoot: FC<TIssueCommentRoot> = observer((props) => { export const IssueCommentRoot: FC<TIssueCommentRoot> = observer((props) => {
const { workspaceSlug, issueId, activityOperations, showAccessSpecifier } = props; const { workspaceSlug, projectId, issueId, activityOperations, showAccessSpecifier } = props;
// hooks // hooks
const { const {
comment: { getCommentsByIssueId }, comment: { getCommentsByIssueId },
@ -28,6 +29,7 @@ export const IssueCommentRoot: FC<TIssueCommentRoot> = observer((props) => {
<div> <div>
{commentIds.map((commentId, index) => ( {commentIds.map((commentId, index) => (
<IssueCommentCard <IssueCommentCard
projectId={projectId}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
commentId={commentId} commentId={commentId}
ends={index === 0 ? "top" : index === commentIds.length - 1 ? "bottom" : undefined} ends={index === 0 ? "top" : index === commentIds.length - 1 ? "bottom" : undefined}

View File

@ -141,12 +141,14 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
{activityTab === "all" ? ( {activityTab === "all" ? (
<div className="space-y-3"> <div className="space-y-3">
<IssueActivityCommentRoot <IssueActivityCommentRoot
projectId={projectId}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
issueId={issueId} issueId={issueId}
activityOperations={activityOperations} activityOperations={activityOperations}
showAccessSpecifier={project.is_deployed} showAccessSpecifier={project.is_deployed}
/> />
<IssueCommentCreate <IssueCommentCreate
projectId={projectId}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
activityOperations={activityOperations} activityOperations={activityOperations}
showAccessSpecifier={project.is_deployed} showAccessSpecifier={project.is_deployed}
@ -157,12 +159,14 @@ export const IssueActivity: FC<TIssueActivity> = observer((props) => {
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
<IssueCommentRoot <IssueCommentRoot
projectId={projectId}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
issueId={issueId} issueId={issueId}
activityOperations={activityOperations} activityOperations={activityOperations}
showAccessSpecifier={project.is_deployed} showAccessSpecifier={project.is_deployed}
/> />
<IssueCommentCreate <IssueCommentCreate
projectId={projectId}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
activityOperations={activityOperations} activityOperations={activityOperations}
showAccessSpecifier={project.is_deployed} showAccessSpecifier={project.is_deployed}

View File

@ -96,7 +96,11 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
} = useApplication(); } = useApplication();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const { areEstimatesEnabledForProject } = useEstimate(); const { areEstimatesEnabledForProject } = useEstimate();
const { mentionHighlights, mentionSuggestions } = useMention(); const { mentionHighlights, mentionSuggestions } = useMention({
workspaceSlug: workspaceSlug as string,
projectId: defaultProjectId,
});
const { const {
issue: { getIssueById }, issue: { getIssueById },
} = useIssueDetail(); } = useIssueDetail();

View File

@ -1,23 +1,27 @@
import useSWR from "swr"; import useSWR from "swr";
import { ProjectMemberService } from "services/project";
import { IProjectMember } from "@plane/types";
import { UserService } from "services/user.service";
import { useRef, useEffect } from "react"; import { useRef, useEffect } from "react";
import { ProjectMemberService } from "services/project";
import { IProjectMember, IUser } from "@plane/types";
import { UserService } from "services/user.service";
export const useMention = ({ workspaceSlug, projectId }: { workspaceSlug: string; projectId: string }) => { export const useMention = ({ workspaceSlug, projectId }: { workspaceSlug: string; projectId: string }) => {
const userService = new UserService(); const userService = new UserService();
const projectMemberService = new ProjectMemberService(); const projectMemberService = new ProjectMemberService();
const { data: projectMembers } = useSWR(["projectMembers", workspaceSlug, projectId], async () => { const { data: projectMembers, isLoading: projectMembersLoading } = useSWR(
const members = await projectMemberService.fetchProjectMembers(workspaceSlug, projectId); ["projectMembers", workspaceSlug, projectId],
const detailedMembers = await Promise.all( async () => {
members.map(async (member) => projectMemberService.getProjectMember(workspaceSlug, projectId, member.id)) const members = await projectMemberService.fetchProjectMembers(workspaceSlug, projectId);
); const detailedMembers = await Promise.all(
return detailedMembers; members.map(async (member) => projectMemberService.getProjectMember(workspaceSlug, projectId, member.id))
}); );
return detailedMembers;
}
);
const { data: user, isLoading: userDataLoading } = useSWR("currentUser", async () => userService.currentUser());
const projectMembersRef = useRef<IProjectMember[] | undefined>(); const projectMembersRef = useRef<IProjectMember[] | undefined>();
const userRef = useRef<IUser | undefined>();
useEffect(() => { useEffect(() => {
if (projectMembers) { if (projectMembers) {
@ -25,13 +29,51 @@ export const useMention = ({ workspaceSlug, projectId }: { workspaceSlug: string
} }
}, [projectMembers]); }, [projectMembers]);
const { data: user } = useSWR("currentUser", async () => userService.currentUser()); useEffect(() => {
if (userRef) {
userRef.current = user;
}
}, [user]);
const mentionHighlights = user ? [user.id] : []; const waitForUserDate = async () =>
new Promise<IUser>((resolve) => {
const checkData = () => {
if (userRef.current) {
resolve(userRef.current);
} else {
setTimeout(checkData, 100);
}
};
checkData();
});
const getMentionSuggestions = () => () => { const mentionHighlights = async () => {
const mentionSuggestions = console.log("isme aaya highlights");
projectMembersRef.current?.map((memberDetails) => ({ if (!userDataLoading && userRef.current) {
return [userRef.current.id];
} else {
const user = await waitForUserDate();
return [user.id];
}
};
// Polling function to wait for projectMembersRef.current to be populated
const waitForData = async () =>
new Promise<IProjectMember[]>((resolve) => {
const checkData = () => {
if (projectMembersRef.current && projectMembersRef.current.length > 0) {
resolve(projectMembersRef.current);
} else {
setTimeout(checkData, 100); // Check every 100ms
}
};
checkData();
});
const mentionSuggestions = async () => {
if (!projectMembersLoading && projectMembersRef.current && projectMembersRef.current.length > 0) {
// If data is already available, return it immediately
return projectMembersRef.current.map((memberDetails) => ({
entity_name: "user_mention", entity_name: "user_mention",
entity_identifier: `${memberDetails?.member?.id}`, entity_identifier: `${memberDetails?.member?.id}`,
type: "User", type: "User",
@ -39,12 +81,25 @@ export const useMention = ({ workspaceSlug, projectId }: { workspaceSlug: string
subtitle: memberDetails?.member?.email ?? "", subtitle: memberDetails?.member?.email ?? "",
avatar: `${memberDetails?.member?.avatar}`, avatar: `${memberDetails?.member?.avatar}`,
redirect_uri: `/${workspaceSlug}/profile/${memberDetails?.member?.id}`, redirect_uri: `/${workspaceSlug}/profile/${memberDetails?.member?.id}`,
})) || []; }));
} else {
return mentionSuggestions; // Wait for data to be available
const members = await waitForData();
console.log("isme aaya", members);
return members.map((memberDetails) => ({
entity_name: "user_mention",
entity_identifier: `${memberDetails?.member?.id}`,
type: "User",
title: `${memberDetails?.member?.display_name}`,
subtitle: memberDetails?.member?.email ?? "",
avatar: `${memberDetails?.member?.avatar}`,
redirect_uri: `/${workspaceSlug}/profile/${memberDetails?.member?.id}`,
}));
}
}; };
return { return {
getMentionSuggestions, mentionSuggestions,
mentionHighlights, mentionHighlights,
}; };
}; };

View File

@ -1,9 +1,9 @@
import { Sparkle } from "lucide-react"; import { Sparkle } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { ReactElement, useEffect, useRef, useState } from "react"; import { ReactElement, useEffect, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import useSWR from "swr";
// hooks // hooks
import { useApplication, useMention, usePage, useUser, useWorkspace } from "hooks/store"; import { useApplication, useMention, usePage, useUser, useWorkspace } from "hooks/store";
@ -26,11 +26,9 @@ import { IPage } from "@plane/types";
import { NextPageWithLayout } from "lib/types"; import { NextPageWithLayout } from "lib/types";
// fetch-keys // fetch-keys
// constants // constants
import { IssuePeekOverview } from "components/issues";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { useProjectPages } from "hooks/store/use-project-specific-pages"; import { useProjectPages } from "hooks/store/use-project-specific-pages";
import { IssuePeekOverview } from "components/issues";
import { ProjectMemberService } from "services/project";
import { UserService } from "services/user.service";
// services // services
const fileService = new FileService(); const fileService = new FileService();
@ -78,6 +76,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
? () => fetchProjectPages(workspaceSlug.toString(), projectId.toString()) ? () => fetchProjectPages(workspaceSlug.toString(), projectId.toString())
: null : null
); );
// fetching archived pages from API // fetching archived pages from API
useSWR( useSWR(
workspaceSlug && projectId ? `ALL_ARCHIVED_PAGES_LIST_${projectId}` : null, workspaceSlug && projectId ? `ALL_ARCHIVED_PAGES_LIST_${projectId}` : null,
@ -86,21 +85,12 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
: null : null
); );
const projectMemberService = new ProjectMemberService();
const { data: projectMembers } = useSWR(["projectMembers", workspaceSlug, projectId], async () => {
const members = await projectMemberService.fetchProjectMembers(workspaceSlug, projectId);
const detailedMembers = await Promise.all(
members.map(async (member) => projectMemberService.getProjectMember(workspaceSlug, projectId, member.id))
);
console.log("ye toh chal", detailedMembers);
return detailedMembers;
});
const pageStore = usePage(pageId as string); const pageStore = usePage(pageId as string);
// store hooks const { mentionHighlights, mentionSuggestions } = useMention({
const { getMentionSuggestions, mentionHighlights, mentionSuggestions } = useMention({ workspaceSlug, projectId }); workspaceSlug: workspaceSlug as string,
projectId: projectId as string,
});
const { setShowAlert } = useReloadConfirmations(pageStore?.isSubmitting === "submitting"); const { setShowAlert } = useReloadConfirmations(pageStore?.isSubmitting === "submitting");
@ -317,7 +307,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
last_updated_at: updated_at, last_updated_at: updated_at,
last_updated_by: updated_by, last_updated_by: updated_by,
}} }}
mentionSuggestions={getMentionSuggestions(projectMembers)} mentionSuggestions={mentionSuggestions}
mentionHighlights={mentionHighlights} mentionHighlights={mentionHighlights}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)} uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.getDeleteImageFunction(workspaceId)} deleteFile={fileService.getDeleteImageFunction(workspaceId)}

View File

@ -6,6 +6,7 @@ import "styles/globals.css";
import "styles/command-pallette.css"; import "styles/command-pallette.css";
import "styles/nprogress.css"; import "styles/nprogress.css";
import "styles/react-datepicker.css"; import "styles/react-datepicker.css";
// constants // constants
import { SITE_TITLE } from "constants/seo-variables"; import { SITE_TITLE } from "constants/seo-variables";
// mobx store provider // mobx store provider