forked from github/plane
[WEB-438] fix: ai insertion behaviour (#3872)
* fixed ai insertion behaviour * replaced all ai popover references to have similar behavior * chore: removed debug statements
This commit is contained in:
parent
cb5198c883
commit
549f6d0943
@ -53,7 +53,7 @@
|
|||||||
"react-moveable": "^0.54.2",
|
"react-moveable": "^0.54.2",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"tiptap-markdown": "^0.8.2"
|
"tiptap-markdown": "^0.8.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.15.3",
|
"@types/node": "18.15.3",
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { Selection } from "@tiptap/pm/state";
|
||||||
|
import { Editor } from "@tiptap/react";
|
||||||
|
import { MutableRefObject } from "react";
|
||||||
|
|
||||||
|
export const insertContentAtSavedSelection = (
|
||||||
|
editorRef: MutableRefObject<Editor | null>,
|
||||||
|
content: string,
|
||||||
|
savedSelection: Selection
|
||||||
|
) => {
|
||||||
|
if (editorRef.current && savedSelection) {
|
||||||
|
editorRef.current
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.insertContentAt(savedSelection?.anchor, content)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
|
import { useEditor as useCustomEditor, Editor } from "@tiptap/react";
|
||||||
import { useImperativeHandle, useRef, MutableRefObject } from "react";
|
import { useImperativeHandle, useRef, MutableRefObject, useState } from "react";
|
||||||
import { CoreEditorProps } from "src/ui/props";
|
import { CoreEditorProps } from "src/ui/props";
|
||||||
import { CoreEditorExtensions } from "src/ui/extensions";
|
import { CoreEditorExtensions } from "src/ui/extensions";
|
||||||
import { EditorProps } from "@tiptap/pm/view";
|
import { EditorProps } from "@tiptap/pm/view";
|
||||||
@ -8,6 +8,8 @@ import { DeleteImage } from "src/types/delete-image";
|
|||||||
import { IMentionSuggestion } from "src/types/mention-suggestion";
|
import { 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";
|
||||||
|
import { Selection } from "@tiptap/pm/state";
|
||||||
|
import { insertContentAtSavedSelection } from "src/helpers/insert-content-at-cursor-position";
|
||||||
|
|
||||||
interface CustomEditorProps {
|
interface CustomEditorProps {
|
||||||
uploadFile: UploadImage;
|
uploadFile: UploadImage;
|
||||||
@ -70,8 +72,10 @@ export const useEditor = ({
|
|||||||
onCreate: async ({ editor }) => {
|
onCreate: async ({ editor }) => {
|
||||||
onStart?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
|
onStart?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
|
||||||
},
|
},
|
||||||
|
onTransaction: async ({ editor }) => {
|
||||||
|
setSavedSelection(editor.state.selection);
|
||||||
|
},
|
||||||
onUpdate: async ({ editor }) => {
|
onUpdate: async ({ editor }) => {
|
||||||
// for instant feedback loop
|
|
||||||
setIsSubmitting?.("submitting");
|
setIsSubmitting?.("submitting");
|
||||||
setShouldShowAlert?.(true);
|
setShouldShowAlert?.(true);
|
||||||
onChange?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
|
onChange?.(editor.getJSON(), getTrimmedHTML(editor.getHTML()));
|
||||||
@ -83,6 +87,8 @@ export const useEditor = ({
|
|||||||
const editorRef: MutableRefObject<Editor | null> = useRef(null);
|
const editorRef: MutableRefObject<Editor | null> = useRef(null);
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
|
|
||||||
|
const [savedSelection, setSavedSelection] = useState<Selection | null>(null);
|
||||||
|
|
||||||
useImperativeHandle(forwardedRef, () => ({
|
useImperativeHandle(forwardedRef, () => ({
|
||||||
clearEditor: () => {
|
clearEditor: () => {
|
||||||
editorRef.current?.commands.clearContent();
|
editorRef.current?.commands.clearContent();
|
||||||
@ -90,6 +96,11 @@ export const useEditor = ({
|
|||||||
setEditorValue: (content: string) => {
|
setEditorValue: (content: string) => {
|
||||||
editorRef.current?.commands.setContent(content);
|
editorRef.current?.commands.setContent(content);
|
||||||
},
|
},
|
||||||
|
setEditorValueAtCursorPosition: (content: string) => {
|
||||||
|
if (savedSelection) {
|
||||||
|
insertContentAtSavedSelection(editorRef, content, savedSelection);
|
||||||
|
}
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
|
@ -55,6 +55,7 @@ interface DocumentEditorProps extends IDocumentEditor {
|
|||||||
interface EditorHandle {
|
interface EditorHandle {
|
||||||
clearEditor: () => void;
|
clearEditor: () => void;
|
||||||
setEditorValue: (content: string) => void;
|
setEditorValue: (content: string) => void;
|
||||||
|
setEditorValueAtCursorPosition: (content: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentEditor = ({
|
const DocumentEditor = ({
|
||||||
|
@ -45,6 +45,7 @@ export interface RichTextEditorProps extends IRichTextEditor {
|
|||||||
interface EditorHandle {
|
interface EditorHandle {
|
||||||
clearEditor: () => void;
|
clearEditor: () => void;
|
||||||
setEditorValue: (content: string) => void;
|
setEditorValue: (content: string) => void;
|
||||||
|
setEditorValueAtCursorPosition: (content: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RichTextEditor = ({
|
const RichTextEditor = ({
|
||||||
|
@ -119,9 +119,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
|||||||
const handleAiAssistance = async (response: string) => {
|
const handleAiAssistance = async (response: string) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
// setValue("description", {});
|
editorRef.current?.setEditorValueAtCursorPosition(response);
|
||||||
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
|
|
||||||
editorRef.current?.setEditorValue(`${watch("description_html")}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAutoGenerateDescription = async () => {
|
const handleAutoGenerateDescription = async () => {
|
||||||
|
@ -180,8 +180,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
const handleAiAssistance = async (response: string) => {
|
const handleAiAssistance = async (response: string) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
|
editorRef.current?.setEditorValueAtCursorPosition(response);
|
||||||
editorRef.current?.setEditorValue(`${watch("description_html")}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAutoGenerateDescription = async () => {
|
const handleAutoGenerateDescription = async () => {
|
||||||
|
@ -53,7 +53,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
|
|
||||||
const { handleSubmit, setValue, watch, getValues, control, reset } = useForm<IPage>({
|
const { handleSubmit, getValues, control, reset } = useForm<IPage>({
|
||||||
defaultValues: { name: "", description_html: "" },
|
defaultValues: { name: "", description_html: "" },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -124,16 +124,13 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
|
|
||||||
const updatePage = async (formData: IPage) => {
|
const updatePage = async (formData: IPage) => {
|
||||||
if (!workspaceSlug || !projectId || !pageId) return;
|
if (!workspaceSlug || !projectId || !pageId) return;
|
||||||
await updateDescriptionAction(formData.description_html);
|
updateDescriptionAction(formData.description_html);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAiAssistance = async (response: string) => {
|
const handleAiAssistance = async (response: string) => {
|
||||||
if (!workspaceSlug || !projectId || !pageId) return;
|
if (!workspaceSlug || !projectId || !pageId) return;
|
||||||
|
|
||||||
const newDescription = `${watch("description_html")}<p>${response}</p>`;
|
editorRef.current?.setEditorValueAtCursorPosition(response);
|
||||||
setValue("description_html", newDescription);
|
|
||||||
editorRef.current?.setEditorValue(newDescription);
|
|
||||||
updateDescriptionAction(newDescription);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const actionCompleteAlert = ({
|
const actionCompleteAlert = ({
|
||||||
|
@ -35,7 +35,7 @@ export interface IPageStore {
|
|||||||
addToFavorites: () => Promise<void>;
|
addToFavorites: () => Promise<void>;
|
||||||
removeFromFavorites: () => Promise<void>;
|
removeFromFavorites: () => Promise<void>;
|
||||||
updateName: (name: string) => Promise<void>;
|
updateName: (name: string) => Promise<void>;
|
||||||
updateDescription: (description: string) => Promise<void>;
|
updateDescription: (description: string) => void;
|
||||||
|
|
||||||
// Reactions
|
// Reactions
|
||||||
disposers: Array<() => void>;
|
disposers: Array<() => void>;
|
||||||
@ -89,7 +89,7 @@ export class PageStore implements IPageStore {
|
|||||||
addToFavorites: action,
|
addToFavorites: action,
|
||||||
removeFromFavorites: action,
|
removeFromFavorites: action,
|
||||||
updateName: action,
|
updateName: action,
|
||||||
updateDescription: action,
|
updateDescription: action.bound,
|
||||||
setIsSubmitting: action,
|
setIsSubmitting: action,
|
||||||
cleanup: action,
|
cleanup: action,
|
||||||
});
|
});
|
||||||
@ -166,7 +166,7 @@ export class PageStore implements IPageStore {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
});
|
});
|
||||||
|
|
||||||
updateDescription = action("updateDescription", async (description_html: string) => {
|
updateDescription = action("updateDescription", (description_html: string) => {
|
||||||
const { projectId, workspaceSlug } = this.rootStore.app.router;
|
const { projectId, workspaceSlug } = this.rootStore.app.router;
|
||||||
if (!projectId || !workspaceSlug) return;
|
if (!projectId || !workspaceSlug) return;
|
||||||
|
|
||||||
|
@ -8153,10 +8153,10 @@ tippy.js@^6.3.1, tippy.js@^6.3.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@popperjs/core" "^2.9.0"
|
"@popperjs/core" "^2.9.0"
|
||||||
|
|
||||||
tiptap-markdown@^0.8.2:
|
tiptap-markdown@^0.8.9:
|
||||||
version "0.8.8"
|
version "0.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/tiptap-markdown/-/tiptap-markdown-0.8.8.tgz#1e25f40b726239dff84b99a53eb1bdf4af0a02f9"
|
resolved "https://registry.yarnpkg.com/tiptap-markdown/-/tiptap-markdown-0.8.9.tgz#e13f3ae9a1b1649f8c28bb3cae4516a53da7492c"
|
||||||
integrity sha512-I2w/IpvCZ1BoR3nQzG0wRK3uGmDv+Ohyr++G24Ma6RzoDYd0TVGXZp0BOODX5Jj4c6heVY8eksahSeAwJMZBeg==
|
integrity sha512-TykSDcsb94VFCzPbSSTfB6Kh2HJi7x4B9J3Jm9uSOAMPy8App1YfrLW/rEJLajTxwMVhWBdOo4nidComSlLQsQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/markdown-it" "^12.2.3"
|
"@types/markdown-it" "^12.2.3"
|
||||||
markdown-it "^13.0.1"
|
markdown-it "^13.0.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user