diff --git a/packages/editor/core/package.json b/packages/editor/core/package.json index 198b21b0f..571fb8588 100644 --- a/packages/editor/core/package.json +++ b/packages/editor/core/package.json @@ -53,7 +53,7 @@ "react-moveable": "^0.54.2", "tailwind-merge": "^1.14.0", "tippy.js": "^6.3.7", - "tiptap-markdown": "^0.8.2" + "tiptap-markdown": "^0.8.9" }, "devDependencies": { "@types/node": "18.15.3", diff --git a/packages/editor/core/src/helpers/insert-content-at-cursor-position.ts b/packages/editor/core/src/helpers/insert-content-at-cursor-position.ts new file mode 100644 index 000000000..062acafcb --- /dev/null +++ b/packages/editor/core/src/helpers/insert-content-at-cursor-position.ts @@ -0,0 +1,17 @@ +import { Selection } from "@tiptap/pm/state"; +import { Editor } from "@tiptap/react"; +import { MutableRefObject } from "react"; + +export const insertContentAtSavedSelection = ( + editorRef: MutableRefObject, + content: string, + savedSelection: Selection +) => { + if (editorRef.current && savedSelection) { + editorRef.current + .chain() + .focus() + .insertContentAt(savedSelection?.anchor, content) + .run(); + } +}; diff --git a/packages/editor/core/src/hooks/use-editor.tsx b/packages/editor/core/src/hooks/use-editor.tsx index c2923c1e9..7e6aa5912 100644 --- a/packages/editor/core/src/hooks/use-editor.tsx +++ b/packages/editor/core/src/hooks/use-editor.tsx @@ -1,5 +1,5 @@ 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 { CoreEditorExtensions } from "src/ui/extensions"; 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 { RestoreImage } from "src/types/restore-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 { uploadFile: UploadImage; @@ -70,8 +72,10 @@ export const useEditor = ({ onCreate: async ({ editor }) => { onStart?.(editor.getJSON(), getTrimmedHTML(editor.getHTML())); }, + onTransaction: async ({ editor }) => { + setSavedSelection(editor.state.selection); + }, onUpdate: async ({ editor }) => { - // for instant feedback loop setIsSubmitting?.("submitting"); setShouldShowAlert?.(true); onChange?.(editor.getJSON(), getTrimmedHTML(editor.getHTML())); @@ -83,6 +87,8 @@ export const useEditor = ({ const editorRef: MutableRefObject = useRef(null); editorRef.current = editor; + const [savedSelection, setSavedSelection] = useState(null); + useImperativeHandle(forwardedRef, () => ({ clearEditor: () => { editorRef.current?.commands.clearContent(); @@ -90,6 +96,11 @@ export const useEditor = ({ setEditorValue: (content: string) => { editorRef.current?.commands.setContent(content); }, + setEditorValueAtCursorPosition: (content: string) => { + if (savedSelection) { + insertContentAtSavedSelection(editorRef, content, savedSelection); + } + }, })); if (!editor) { diff --git a/packages/editor/document-editor/src/ui/index.tsx b/packages/editor/document-editor/src/ui/index.tsx index 2491e04c7..e9f6d884b 100644 --- a/packages/editor/document-editor/src/ui/index.tsx +++ b/packages/editor/document-editor/src/ui/index.tsx @@ -55,6 +55,7 @@ interface DocumentEditorProps extends IDocumentEditor { interface EditorHandle { clearEditor: () => void; setEditorValue: (content: string) => void; + setEditorValueAtCursorPosition: (content: string) => void; } const DocumentEditor = ({ diff --git a/packages/editor/rich-text-editor/src/ui/index.tsx b/packages/editor/rich-text-editor/src/ui/index.tsx index 4bcb340fd..2aff5d265 100644 --- a/packages/editor/rich-text-editor/src/ui/index.tsx +++ b/packages/editor/rich-text-editor/src/ui/index.tsx @@ -45,6 +45,7 @@ export interface RichTextEditorProps extends IRichTextEditor { interface EditorHandle { clearEditor: () => void; setEditorValue: (content: string) => void; + setEditorValueAtCursorPosition: (content: string) => void; } const RichTextEditor = ({ diff --git a/web/components/inbox/modals/create-issue-modal.tsx b/web/components/inbox/modals/create-issue-modal.tsx index 5a3e614a9..2603b712e 100644 --- a/web/components/inbox/modals/create-issue-modal.tsx +++ b/web/components/inbox/modals/create-issue-modal.tsx @@ -119,9 +119,7 @@ export const CreateInboxIssueModal: React.FC = observer((props) => { const handleAiAssistance = async (response: string) => { if (!workspaceSlug || !projectId) return; - // setValue("description", {}); - setValue("description_html", `${watch("description_html")}

${response}

`); - editorRef.current?.setEditorValue(`${watch("description_html")}`); + editorRef.current?.setEditorValueAtCursorPosition(response); }; const handleAutoGenerateDescription = async () => { diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index 527ebd0e1..03a9ae5b0 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -180,8 +180,7 @@ export const IssueFormRoot: FC = observer((props) => { const handleAiAssistance = async (response: string) => { if (!workspaceSlug || !projectId) return; - setValue("description_html", `${watch("description_html")}

${response}

`); - editorRef.current?.setEditorValue(`${watch("description_html")}`); + editorRef.current?.setEditorValueAtCursorPosition(response); }; const handleAutoGenerateDescription = async () => { diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 3a133ee50..16dba79b3 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -53,7 +53,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { membership: { currentProjectRole }, } = useUser(); - const { handleSubmit, setValue, watch, getValues, control, reset } = useForm({ + const { handleSubmit, getValues, control, reset } = useForm({ defaultValues: { name: "", description_html: "" }, }); @@ -124,16 +124,13 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { const updatePage = async (formData: IPage) => { if (!workspaceSlug || !projectId || !pageId) return; - await updateDescriptionAction(formData.description_html); + updateDescriptionAction(formData.description_html); }; const handleAiAssistance = async (response: string) => { if (!workspaceSlug || !projectId || !pageId) return; - const newDescription = `${watch("description_html")}

${response}

`; - setValue("description_html", newDescription); - editorRef.current?.setEditorValue(newDescription); - updateDescriptionAction(newDescription); + editorRef.current?.setEditorValueAtCursorPosition(response); }; const actionCompleteAlert = ({ diff --git a/web/store/page.store.ts b/web/store/page.store.ts index ae416237f..30fc3d157 100644 --- a/web/store/page.store.ts +++ b/web/store/page.store.ts @@ -35,7 +35,7 @@ export interface IPageStore { addToFavorites: () => Promise; removeFromFavorites: () => Promise; updateName: (name: string) => Promise; - updateDescription: (description: string) => Promise; + updateDescription: (description: string) => void; // Reactions disposers: Array<() => void>; @@ -89,7 +89,7 @@ export class PageStore implements IPageStore { addToFavorites: action, removeFromFavorites: action, updateName: action, - updateDescription: action, + updateDescription: action.bound, setIsSubmitting: action, cleanup: action, }); @@ -166,7 +166,7 @@ export class PageStore implements IPageStore { this.name = name; }); - updateDescription = action("updateDescription", async (description_html: string) => { + updateDescription = action("updateDescription", (description_html: string) => { const { projectId, workspaceSlug } = this.rootStore.app.router; if (!projectId || !workspaceSlug) return; diff --git a/yarn.lock b/yarn.lock index c8cfcffd4..66fef83d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8153,10 +8153,10 @@ tippy.js@^6.3.1, tippy.js@^6.3.7: dependencies: "@popperjs/core" "^2.9.0" -tiptap-markdown@^0.8.2: - version "0.8.8" - resolved "https://registry.yarnpkg.com/tiptap-markdown/-/tiptap-markdown-0.8.8.tgz#1e25f40b726239dff84b99a53eb1bdf4af0a02f9" - integrity sha512-I2w/IpvCZ1BoR3nQzG0wRK3uGmDv+Ohyr++G24Ma6RzoDYd0TVGXZp0BOODX5Jj4c6heVY8eksahSeAwJMZBeg== +tiptap-markdown@^0.8.9: + version "0.8.9" + resolved "https://registry.yarnpkg.com/tiptap-markdown/-/tiptap-markdown-0.8.9.tgz#e13f3ae9a1b1649f8c28bb3cae4516a53da7492c" + integrity sha512-TykSDcsb94VFCzPbSSTfB6Kh2HJi7x4B9J3Jm9uSOAMPy8App1YfrLW/rEJLajTxwMVhWBdOo4nidComSlLQsQ== dependencies: "@types/markdown-it" "^12.2.3" markdown-it "^13.0.1"