mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
mentions loading state part done
This commit is contained in:
parent
875eb054a2
commit
a3033203af
@ -1,3 +1,4 @@
|
|||||||
|
import { Editor, Range } from "@tiptap/react";
|
||||||
export type IMentionSuggestion = {
|
export type IMentionSuggestion = {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
@ -7,4 +8,9 @@ export type IMentionSuggestion = {
|
|||||||
redirect_uri: string;
|
redirect_uri: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CommandProps = {
|
||||||
|
editor: Editor;
|
||||||
|
range: Range;
|
||||||
|
};
|
||||||
|
|
||||||
export type IMentionHighlight = string;
|
export type IMentionHighlight = string;
|
||||||
|
@ -35,79 +35,81 @@ export const CoreEditorExtensions = (
|
|||||||
deleteFile: DeleteImage,
|
deleteFile: DeleteImage,
|
||||||
restoreFile: RestoreImage,
|
restoreFile: RestoreImage,
|
||||||
cancelUploadImage?: () => any
|
cancelUploadImage?: () => any
|
||||||
) => [
|
) => {
|
||||||
StarterKit.configure({
|
return [
|
||||||
bulletList: {
|
StarterKit.configure({
|
||||||
HTMLAttributes: {
|
bulletList: {
|
||||||
class: "list-disc list-outside leading-3 -mt-2",
|
HTMLAttributes: {
|
||||||
|
class: "list-disc list-outside leading-3 -mt-2",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
orderedList: {
|
||||||
orderedList: {
|
HTMLAttributes: {
|
||||||
HTMLAttributes: {
|
class: "list-decimal list-outside leading-3 -mt-2",
|
||||||
class: "list-decimal list-outside leading-3 -mt-2",
|
},
|
||||||
},
|
},
|
||||||
},
|
listItem: {
|
||||||
listItem: {
|
HTMLAttributes: {
|
||||||
HTMLAttributes: {
|
class: "leading-normal -mb-2",
|
||||||
class: "leading-normal -mb-2",
|
},
|
||||||
},
|
},
|
||||||
},
|
code: false,
|
||||||
code: false,
|
codeBlock: false,
|
||||||
codeBlock: false,
|
horizontalRule: {
|
||||||
horizontalRule: {
|
HTMLAttributes: { class: "mt-4 mb-4" },
|
||||||
HTMLAttributes: { class: "mt-4 mb-4" },
|
},
|
||||||
},
|
blockquote: false,
|
||||||
blockquote: false,
|
dropcursor: {
|
||||||
dropcursor: {
|
color: "rgba(var(--color-text-100))",
|
||||||
color: "rgba(var(--color-text-100))",
|
width: 2,
|
||||||
width: 2,
|
},
|
||||||
},
|
}),
|
||||||
}),
|
CustomQuoteExtension.configure({
|
||||||
CustomQuoteExtension.configure({
|
HTMLAttributes: { className: "border-l-4 border-custom-border-300" },
|
||||||
HTMLAttributes: { className: "border-l-4 border-custom-border-300" },
|
}),
|
||||||
}),
|
CustomKeymap,
|
||||||
CustomKeymap,
|
ListKeymap,
|
||||||
ListKeymap,
|
CustomLinkExtension.configure({
|
||||||
CustomLinkExtension.configure({
|
openOnClick: true,
|
||||||
openOnClick: true,
|
autolink: true,
|
||||||
autolink: true,
|
linkOnPaste: true,
|
||||||
linkOnPaste: true,
|
protocols: ["http", "https"],
|
||||||
protocols: ["http", "https"],
|
validate: (url: string) => isValidHttpUrl(url),
|
||||||
validate: (url: string) => isValidHttpUrl(url),
|
HTMLAttributes: {
|
||||||
HTMLAttributes: {
|
class:
|
||||||
class:
|
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
|
||||||
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
|
},
|
||||||
},
|
}),
|
||||||
}),
|
ImageExtension(deleteFile, restoreFile, cancelUploadImage).configure({
|
||||||
ImageExtension(deleteFile, restoreFile, cancelUploadImage).configure({
|
HTMLAttributes: {
|
||||||
HTMLAttributes: {
|
class: "rounded-lg border border-custom-border-300",
|
||||||
class: "rounded-lg border border-custom-border-300",
|
},
|
||||||
},
|
}),
|
||||||
}),
|
TiptapUnderline,
|
||||||
TiptapUnderline,
|
TextStyle,
|
||||||
TextStyle,
|
Color,
|
||||||
Color,
|
TaskList.configure({
|
||||||
TaskList.configure({
|
HTMLAttributes: {
|
||||||
HTMLAttributes: {
|
class: "not-prose pl-2",
|
||||||
class: "not-prose pl-2",
|
},
|
||||||
},
|
}),
|
||||||
}),
|
TaskItem.configure({
|
||||||
TaskItem.configure({
|
HTMLAttributes: {
|
||||||
HTMLAttributes: {
|
class: "flex items-start my-4",
|
||||||
class: "flex items-start my-4",
|
},
|
||||||
},
|
nested: true,
|
||||||
nested: true,
|
}),
|
||||||
}),
|
CustomCodeBlockExtension,
|
||||||
CustomCodeBlockExtension,
|
CustomCodeInlineExtension,
|
||||||
CustomCodeInlineExtension,
|
Markdown.configure({
|
||||||
Markdown.configure({
|
html: true,
|
||||||
html: true,
|
transformCopiedText: true,
|
||||||
transformCopiedText: true,
|
transformPastedText: true,
|
||||||
transformPastedText: true,
|
}),
|
||||||
}),
|
Table,
|
||||||
Table,
|
TableHeader,
|
||||||
TableHeader,
|
TableCell,
|
||||||
TableCell,
|
TableRow,
|
||||||
TableRow,
|
Mentions(mentionConfig.mentionSuggestions, mentionConfig.mentionHighlights, false),
|
||||||
Mentions(mentionConfig.mentionSuggestions, mentionConfig.mentionHighlights, false),
|
];
|
||||||
];
|
};
|
||||||
|
@ -32,6 +32,12 @@ export const CustomMention = Mention.extend<CustomMentionOptions>({
|
|||||||
redirect_uri: {
|
redirect_uri: {
|
||||||
default: "/",
|
default: "/",
|
||||||
},
|
},
|
||||||
|
entity_identifier: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
entity_name: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -43,17 +49,6 @@ export const CustomMention = Mention.extend<CustomMentionOptions>({
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: "mention-component",
|
tag: "mention-component",
|
||||||
getAttrs: (node: string | HTMLElement) => {
|
|
||||||
if (typeof node === "string") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: node.getAttribute("data-mention-id") || "",
|
|
||||||
target: node.getAttribute("data-mention-target") || "",
|
|
||||||
label: node.innerText.slice(1) || "",
|
|
||||||
redirect_uri: node.getAttribute("redirect_uri"),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
@ -1,15 +1,115 @@
|
|||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import { Suggestion } from "src/ui/mentions/suggestion";
|
|
||||||
import { CustomMention } from "src/ui/mentions/custom";
|
import { CustomMention } from "src/ui/mentions/custom";
|
||||||
import { IMentionHighlight } from "src/types/mention-suggestion";
|
import { IMentionHighlight, IMentionSuggestion } from "src/types/mention-suggestion";
|
||||||
|
import { ReactRenderer } from "@tiptap/react";
|
||||||
|
import { Editor } from "@tiptap/core";
|
||||||
|
import tippy from "tippy.js";
|
||||||
|
|
||||||
export const Mentions = (mentionSuggestions: IMentionSuggestion[], mentionHighlights: IMentionHighlight[], readonly) =>
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
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 = (
|
||||||
|
mentionSuggestions: () => Promise<IMentionSuggestion[]>,
|
||||||
|
mentionHighlights: IMentionHighlight[],
|
||||||
|
readonly: boolean
|
||||||
|
) =>
|
||||||
CustomMention.configure({
|
CustomMention.configure({
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: "mention",
|
class: "mention",
|
||||||
},
|
},
|
||||||
readonly: readonly,
|
readonly: readonly,
|
||||||
mentionHighlights: mentionHighlights,
|
mentionHighlights: mentionHighlights,
|
||||||
suggestion: Suggestion(mentionSuggestions),
|
suggestion: {
|
||||||
|
items: ({ query }) => {
|
||||||
|
const suggestions = mentionSuggestions();
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
render: () => {
|
||||||
|
let reactRenderer: ReactRenderer | null = null;
|
||||||
|
let popup: any | null = null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
onStart: (props: { editor: Editor; clientRect: DOMRect }) => {
|
||||||
|
props.editor.storage.mentionsOpen = true;
|
||||||
|
reactRenderer = new ReactRenderer(MentionList, {
|
||||||
|
props,
|
||||||
|
editor: props.editor,
|
||||||
|
});
|
||||||
|
// @ts-ignore
|
||||||
|
popup = tippy("body", {
|
||||||
|
getReferenceClientRect: props.clientRect,
|
||||||
|
appendTo: () => document.querySelector("#editor-container"),
|
||||||
|
content: reactRenderer.element,
|
||||||
|
showOnCreate: true,
|
||||||
|
interactive: true,
|
||||||
|
trigger: "manual",
|
||||||
|
placement: "bottom-start",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => {
|
||||||
|
reactRenderer?.updateProps(props);
|
||||||
|
|
||||||
|
popup &&
|
||||||
|
popup[0].setProps({
|
||||||
|
getReferenceClientRect: props.clientRect,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onKeyDown: (props: { event: KeyboardEvent }) => {
|
||||||
|
if (props.event.key === "Escape") {
|
||||||
|
popup?.[0].hide();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigationKeys = ["ArrowUp", "ArrowDown", "Enter"];
|
||||||
|
|
||||||
|
if (navigationKeys.includes(props.event.key)) {
|
||||||
|
// @ts-ignore
|
||||||
|
reactRenderer?.ref?.onKeyDown(props);
|
||||||
|
event?.stopPropagation();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onExit: (props: { editor: Editor; event: KeyboardEvent }) => {
|
||||||
|
props.editor.storage.mentionsOpen = false;
|
||||||
|
popup?.[0].destroy();
|
||||||
|
reactRenderer?.destroy();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -19,6 +19,8 @@ export const MentionList = forwardRef((props: MentionListProps, ref) => {
|
|||||||
props.command({
|
props.command({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.title,
|
label: item.title,
|
||||||
|
entity_identifier: item.entity_identifier,
|
||||||
|
entity_name: item.entity_name,
|
||||||
target: "users",
|
target: "users",
|
||||||
redirect_uri: item.redirect_uri,
|
redirect_uri: item.redirect_uri,
|
||||||
});
|
});
|
||||||
|
@ -20,13 +20,13 @@ export const MentionNodeView = (props) => {
|
|||||||
<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 ? highlights.includes(props.node.attrs.id) : false,
|
"bg-yellow-500/20 text-yellow-500": highlights
|
||||||
|
? highlights.includes(props.node.attrs.entity_identifier)
|
||||||
|
: 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)
|
||||||
})}
|
})}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
data-mention-target={props.node.attrs.target}
|
|
||||||
data-mention-id={props.node.attrs.id}
|
|
||||||
>
|
>
|
||||||
@{props.node.attrs.label}
|
@{props.node.attrs.label}
|
||||||
</span>
|
</span>
|
||||||
|
@ -2,65 +2,80 @@ import { ReactRenderer } from "@tiptap/react";
|
|||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "@tiptap/core";
|
||||||
import tippy from "tippy.js";
|
import tippy from "tippy.js";
|
||||||
|
|
||||||
import { MentionList } from "src/ui/mentions/mention-list";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { IMentionSuggestion } from "src/types/mention-suggestion";
|
import { IMentionSuggestion } from "src/types/mention-suggestion";
|
||||||
|
import { MentionList } from "src/ui/mentions/mention-list";
|
||||||
|
|
||||||
export const Suggestion = (suggestions: IMentionSuggestion[]) => ({
|
export const getSuggestionItems = (suggestions: IMentionSuggestion[]) => {
|
||||||
items: ({ query }: { query: string }) =>
|
return ({ query }: { query: string }) => {
|
||||||
suggestions.filter((suggestion) => suggestion.title.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5),
|
const mappedSuggestions: IMentionSuggestion[] = suggestions.map((suggestion): IMentionSuggestion => {
|
||||||
render: () => {
|
const transactionId = uuidv4();
|
||||||
let reactRenderer: ReactRenderer | null = null;
|
return {
|
||||||
let popup: any | null = null;
|
...suggestion,
|
||||||
|
id: transactionId,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return mappedSuggestions
|
||||||
|
.filter((suggestion) => suggestion.title.toLowerCase().startsWith(query.toLowerCase()))
|
||||||
|
.slice(0, 5);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
// export const Suggestion = (suggestions: IMentionSuggestion[]) => ({
|
||||||
onStart: (props: { editor: Editor; clientRect: DOMRect }) => {
|
// items: getSuggestionItems(suggestions),
|
||||||
props.editor.storage.mentionsOpen = true;
|
// render: () => {
|
||||||
reactRenderer = new ReactRenderer(MentionList, {
|
// let reactRenderer: ReactRenderer | null = null;
|
||||||
props,
|
// let popup: any | null = null;
|
||||||
editor: props.editor,
|
//
|
||||||
});
|
// return {
|
||||||
// @ts-ignore
|
// onStart: (props: { editor: Editor; clientRect: DOMRect }) => {
|
||||||
popup = tippy("body", {
|
// props.editor.storage.mentionsOpen = true;
|
||||||
getReferenceClientRect: props.clientRect,
|
// reactRenderer = new ReactRenderer(MentionList, {
|
||||||
appendTo: () => document.querySelector("#editor-container"),
|
// props,
|
||||||
content: reactRenderer.element,
|
// editor: props.editor,
|
||||||
showOnCreate: true,
|
// });
|
||||||
interactive: true,
|
// // @ts-ignore
|
||||||
trigger: "manual",
|
// popup = tippy("body", {
|
||||||
placement: "bottom-start",
|
// getReferenceClientRect: props.clientRect,
|
||||||
});
|
// appendTo: () => document.querySelector("#editor-container"),
|
||||||
},
|
// content: reactRenderer.element,
|
||||||
|
// showOnCreate: true,
|
||||||
onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => {
|
// interactive: true,
|
||||||
reactRenderer?.updateProps(props);
|
// trigger: "manual",
|
||||||
|
// placement: "bottom-start",
|
||||||
popup &&
|
// });
|
||||||
popup[0].setProps({
|
// },
|
||||||
getReferenceClientRect: props.clientRect,
|
//
|
||||||
});
|
// onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => {
|
||||||
},
|
// reactRenderer?.updateProps(props);
|
||||||
onKeyDown: (props: { event: KeyboardEvent }) => {
|
//
|
||||||
if (props.event.key === "Escape") {
|
// popup &&
|
||||||
popup?.[0].hide();
|
// popup[0].setProps({
|
||||||
|
// getReferenceClientRect: props.clientRect,
|
||||||
return true;
|
// });
|
||||||
}
|
// },
|
||||||
|
// onKeyDown: (props: { event: KeyboardEvent }) => {
|
||||||
const navigationKeys = ["ArrowUp", "ArrowDown", "Enter"];
|
// if (props.event.key === "Escape") {
|
||||||
|
// popup?.[0].hide();
|
||||||
if (navigationKeys.includes(props.event.key)) {
|
//
|
||||||
// @ts-ignore
|
// return true;
|
||||||
reactRenderer?.ref?.onKeyDown(props);
|
// }
|
||||||
event?.stopPropagation();
|
//
|
||||||
return true;
|
// const navigationKeys = ["ArrowUp", "ArrowDown", "Enter"];
|
||||||
}
|
//
|
||||||
return false;
|
// if (navigationKeys.includes(props.event.key)) {
|
||||||
},
|
// // @ts-ignore
|
||||||
onExit: (props: { editor: Editor; event: KeyboardEvent }) => {
|
// reactRenderer?.ref?.onKeyDown(props);
|
||||||
props.editor.storage.mentionsOpen = false;
|
// event?.stopPropagation();
|
||||||
popup?.[0].destroy();
|
// return true;
|
||||||
reactRenderer?.destroy();
|
// }
|
||||||
},
|
// return false;
|
||||||
};
|
// },
|
||||||
},
|
// onExit: (props: { editor: Editor; event: KeyboardEvent }) => {
|
||||||
});
|
// props.editor.storage.mentionsOpen = false;
|
||||||
|
// popup?.[0].destroy();
|
||||||
|
// reactRenderer?.destroy();
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { UploadImage, DeleteImage, RestoreImage, getEditorClassNames, useEditor } from "@plane/editor-core";
|
import {
|
||||||
|
UploadImage,
|
||||||
|
DeleteImage,
|
||||||
|
RestoreImage,
|
||||||
|
getEditorClassNames,
|
||||||
|
useEditor,
|
||||||
|
IMentionSuggestion,
|
||||||
|
} from "@plane/editor-core";
|
||||||
import { DocumentEditorExtensions } from "src/ui/extensions";
|
import { DocumentEditorExtensions } from "src/ui/extensions";
|
||||||
import { IDuplicationConfig, IPageArchiveConfig, IPageLockConfig } from "src/types/menu-actions";
|
import { IDuplicationConfig, IPageArchiveConfig, IPageLockConfig } from "src/types/menu-actions";
|
||||||
import { EditorHeader } from "src/ui/components/editor-header";
|
import { EditorHeader } from "src/ui/components/editor-header";
|
||||||
@ -43,6 +50,9 @@ interface IDocumentEditor {
|
|||||||
debouncedUpdatesEnabled?: boolean;
|
debouncedUpdatesEnabled?: boolean;
|
||||||
isSubmitting: "submitting" | "submitted" | "saved";
|
isSubmitting: "submitting" | "submitted" | "saved";
|
||||||
|
|
||||||
|
mentionHighlights?: string[];
|
||||||
|
mentionSuggestions?: IMentionSuggestion[];
|
||||||
|
|
||||||
// embed configuration
|
// embed configuration
|
||||||
duplicationConfig?: IDuplicationConfig;
|
duplicationConfig?: IDuplicationConfig;
|
||||||
pageLockConfig?: IPageLockConfig;
|
pageLockConfig?: IPageLockConfig;
|
||||||
@ -66,6 +76,8 @@ const DocumentEditor = ({
|
|||||||
editorContentCustomClassNames,
|
editorContentCustomClassNames,
|
||||||
value,
|
value,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
|
mentionHighlights,
|
||||||
|
mentionSuggestions,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
restoreFile,
|
restoreFile,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
@ -109,6 +121,8 @@ const DocumentEditor = ({
|
|||||||
cancelUploadImage,
|
cancelUploadImage,
|
||||||
rerenderOnPropsChange,
|
rerenderOnPropsChange,
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
|
mentionSuggestions,
|
||||||
|
mentionHighlights,
|
||||||
extensions: DocumentEditorExtensions(uploadFile, setHideDragHandleFunction, setIsSubmitting),
|
extensions: DocumentEditorExtensions(uploadFile, setHideDragHandleFunction, setIsSubmitting),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ interface IDocumentReadOnlyEditor {
|
|||||||
documentDetails: DocumentDetails;
|
documentDetails: DocumentDetails;
|
||||||
pageLockConfig?: IPageLockConfig;
|
pageLockConfig?: IPageLockConfig;
|
||||||
pageArchiveConfig?: IPageArchiveConfig;
|
pageArchiveConfig?: IPageArchiveConfig;
|
||||||
|
mentionHighlights?: string[];
|
||||||
|
|
||||||
pageDuplicationConfig?: IDuplicationConfig;
|
pageDuplicationConfig?: IDuplicationConfig;
|
||||||
onActionCompleteHandler: (action: {
|
onActionCompleteHandler: (action: {
|
||||||
title: string;
|
title: string;
|
||||||
@ -44,6 +46,7 @@ const DocumentReadOnlyEditor = ({
|
|||||||
borderOnFocus,
|
borderOnFocus,
|
||||||
customClassName,
|
customClassName,
|
||||||
value,
|
value,
|
||||||
|
mentionHighlights,
|
||||||
documentDetails,
|
documentDetails,
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
pageDuplicationConfig,
|
pageDuplicationConfig,
|
||||||
@ -58,6 +61,7 @@ const DocumentReadOnlyEditor = ({
|
|||||||
|
|
||||||
const editor = useReadOnlyEditor({
|
const editor = useReadOnlyEditor({
|
||||||
value,
|
value,
|
||||||
|
mentionHighlights,
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
rerenderOnPropsChange,
|
rerenderOnPropsChange,
|
||||||
extensions: [IssueWidgetPlaceholder()],
|
extensions: [IssueWidgetPlaceholder()],
|
||||||
|
@ -81,7 +81,6 @@ export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
|
|||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<LiteTextEditorWithRef
|
<LiteTextEditorWithRef
|
||||||
onEnterKeyPress={(e) => {
|
onEnterKeyPress={(e) => {
|
||||||
console.log("yo");
|
|
||||||
handleSubmit(onSubmit)(e);
|
handleSubmit(onSubmit)(e);
|
||||||
}}
|
}}
|
||||||
cancelUploadImage={fileService.cancelUpload}
|
cancelUploadImage={fileService.cancelUpload}
|
||||||
|
@ -1,11 +1,50 @@
|
|||||||
import { useContext } from "react";
|
import useSWR from "swr";
|
||||||
// mobx store
|
|
||||||
import { StoreContext } from "contexts/store-context";
|
|
||||||
// types
|
|
||||||
import { IMentionStore } from "store/mention.store";
|
|
||||||
|
|
||||||
export const useMention = (): IMentionStore => {
|
import { ProjectMemberService } from "services/project";
|
||||||
const context = useContext(StoreContext);
|
import { IProjectMember } from "@plane/types";
|
||||||
if (context === undefined) throw new Error("useMention must be used within StoreProvider");
|
import { UserService } from "services/user.service";
|
||||||
return context.mention;
|
import { useRef, useEffect } from "react";
|
||||||
|
|
||||||
|
export const useMention = ({ workspaceSlug, projectId }: { workspaceSlug: string; projectId: string }) => {
|
||||||
|
const userService = new UserService();
|
||||||
|
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))
|
||||||
|
);
|
||||||
|
return detailedMembers;
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectMembersRef = useRef<IProjectMember[] | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectMembers) {
|
||||||
|
projectMembersRef.current = projectMembers;
|
||||||
|
}
|
||||||
|
}, [projectMembers]);
|
||||||
|
|
||||||
|
const { data: user } = useSWR("currentUser", async () => userService.currentUser());
|
||||||
|
|
||||||
|
const mentionHighlights = user ? [user.id] : [];
|
||||||
|
|
||||||
|
const getMentionSuggestions = () => () => {
|
||||||
|
const mentionSuggestions =
|
||||||
|
projectMembersRef.current?.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 mentionSuggestions;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
getMentionSuggestions,
|
||||||
|
mentionHighlights,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,7 @@ import { ReactElement, useEffect, useRef, useState } from "react";
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// hooks
|
// hooks
|
||||||
|
|
||||||
import { useApplication, usePage, useUser, useWorkspace } from "hooks/store";
|
import { useApplication, useMention, usePage, useUser, useWorkspace } from "hooks/store";
|
||||||
import useReloadConfirmations from "hooks/use-reload-confirmation";
|
import useReloadConfirmations from "hooks/use-reload-confirmation";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
@ -29,6 +29,8 @@ import { NextPageWithLayout } from "lib/types";
|
|||||||
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 { 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();
|
||||||
@ -84,8 +86,22 @@ 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 { getMentionSuggestions, mentionHighlights, mentionSuggestions } = useMention({ workspaceSlug, projectId });
|
||||||
|
|
||||||
const { setShowAlert } = useReloadConfirmations(pageStore?.isSubmitting === "submitting");
|
const { setShowAlert } = useReloadConfirmations(pageStore?.isSubmitting === "submitting");
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
@ -273,6 +289,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,
|
||||||
}}
|
}}
|
||||||
|
mentionHighlights={mentionHighlights}
|
||||||
pageLockConfig={userCanLock && !archived_at ? { action: unlockPage, is_locked: is_locked } : undefined}
|
pageLockConfig={userCanLock && !archived_at ? { action: unlockPage, is_locked: is_locked } : undefined}
|
||||||
pageDuplicationConfig={userCanDuplicate && !archived_at ? { action: duplicate_page } : undefined}
|
pageDuplicationConfig={userCanDuplicate && !archived_at ? { action: duplicate_page } : undefined}
|
||||||
pageArchiveConfig={
|
pageArchiveConfig={
|
||||||
@ -300,6 +317,8 @@ 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)}
|
||||||
|
mentionHighlights={mentionHighlights}
|
||||||
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
|
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
|
||||||
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
|
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
|
||||||
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
|
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
|
||||||
|
@ -33,9 +33,12 @@ export class MentionStore implements IMentionStore {
|
|||||||
|
|
||||||
const suggestions = (projectMemberIds ?? [])?.map((memberId) => {
|
const suggestions = (projectMemberIds ?? [])?.map((memberId) => {
|
||||||
const memberDetails = this.rootStore.memberRoot.project.getProjectMemberDetails(memberId);
|
const memberDetails = this.rootStore.memberRoot.project.getProjectMemberDetails(memberId);
|
||||||
|
// __AUTO_GENERATED_PRINT_VAR_START__
|
||||||
|
console.log("MentionStore#mentionSuggestions#(anon) memberDetails are: %s", memberDetails?.member.id); // __AUTO_GENERATED_PRINT_VAR_END__
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `${memberDetails?.member?.id}`,
|
entity_name: "user_mention",
|
||||||
|
entity_identifier: `${memberDetails?.member?.id}`,
|
||||||
type: "User",
|
type: "User",
|
||||||
title: `${memberDetails?.member?.display_name}`,
|
title: `${memberDetails?.member?.display_name}`,
|
||||||
subtitle: memberDetails?.member?.email ?? "",
|
subtitle: memberDetails?.member?.email ?? "",
|
||||||
|
18
yarn.lock
18
yarn.lock
@ -5012,7 +5012,7 @@ fault@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
format "^0.2.0"
|
format "^0.2.0"
|
||||||
|
|
||||||
fflate@^0.4.1:
|
fflate@^0.4.8:
|
||||||
version "0.4.8"
|
version "0.4.8"
|
||||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
|
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
|
||||||
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
|
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
|
||||||
@ -7171,12 +7171,18 @@ postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.29:
|
|||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
posthog-js@^1.88.4:
|
posthog-js@^1.105.0:
|
||||||
version "1.96.1"
|
version "1.108.3"
|
||||||
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.96.1.tgz#4f9719a24e4e14037b0e72d430194d7cdb576447"
|
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.108.3.tgz#774353d7ad594b68e6f5e6cce0fe8b583562f455"
|
||||||
integrity sha512-kv1vQqYMt2BV3YHS+wxsbGuP+tz+M3y1AzNhz8TfkpY1HT8W/ONT0i0eQpeRr9Y+d4x/fZ6M4cXG5GMvi9lRCA==
|
integrity sha512-Vi9lX/MhovsKIEdj2aJ5ioku9U/eMGY8/DzKf4EpyrElxPPdabAdCDRUa81eAqxC6npkOpkHskawUPLg20le4Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
fflate "^0.4.1"
|
fflate "^0.4.8"
|
||||||
|
preact "^10.19.3"
|
||||||
|
|
||||||
|
preact@^10.19.3:
|
||||||
|
version "10.19.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.6.tgz#66007b67aad4d11899f583df1b0116d94a89b8f5"
|
||||||
|
integrity sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==
|
||||||
|
|
||||||
prebuild-install@^7.1.1:
|
prebuild-install@^7.1.1:
|
||||||
version "7.1.1"
|
version "7.1.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user