Skip to content

Commit 3d4abe2

Browse files
committed
updating text based on external state
1 parent ee9e850 commit 3d4abe2

3 files changed

Lines changed: 69 additions & 18 deletions

File tree

examples/next-openai/src/app/components/vacation-notes.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ export function VacationNotes(): JSX.Element {
66
const [text, setText] = useState("");
77

88
return (
9-
<CopilotTextarea
10-
className="px-4 py-4"
11-
value={text}
12-
onChange={(value: string) => setText(value)}
13-
placeholder="What are your plans for your vacation?"
14-
autosuggestionsConfig={{
15-
textareaPurpose: "Notes for my upcoming vacation",
16-
debounceTime: 0.7,
17-
acceptAutosuggestionKey: "Tab",
18-
contextCategories: [],
19-
disableWhenEmpty: true,
20-
}}
21-
/>
9+
<>
10+
<CopilotTextarea
11+
className="px-4 py-4"
12+
value={text}
13+
onChange={(value: string) => setText(value)}
14+
placeholder="What are your plans for your vacation?"
15+
autosuggestionsConfig={{
16+
textareaPurpose: "Notes for my upcoming vacation",
17+
debounceTime: 0.7,
18+
acceptAutosuggestionKey: "Tab",
19+
contextCategories: [],
20+
disableWhenEmpty: true,
21+
}}
22+
/>
23+
</>
2224
);
2325
}

packages/react-textarea/src/components/copilot-textarea/copilot-textarea.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// This example is for an Editor with `ReactEditor` and `HistoryEditor`
22
import { Descendant, Editor } from "slate";
33
import { Editable, RenderPlaceholderProps, Slate } from "slate-react";
4-
import { useCallback, useEffect, useMemo, useRef } from "react";
4+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
55
import { useAutosuggestions } from "../../hooks/use-autosuggestions";
66
import { AutosuggestionState } from "../../types/autosuggestion-state";
77
import { clearAutocompletionsFromEditor } from "../../lib/slatejs-edits/clear-autocompletions";
@@ -14,7 +14,11 @@ import {
1414
defaultAutosuggestionsConfig,
1515
} from "../../types/autosuggestions-config";
1616
import { makeRenderPlaceholderFunction } from "./render-placeholder";
17-
import { getFullEditorTextWithNewlines, getTextAroundCursor } from "../../lib/get-text-around-cursor";
17+
import {
18+
getFullEditorTextWithNewlines,
19+
getTextAroundCursor,
20+
} from "../../lib/get-text-around-cursor";
21+
import { replaceEditorText } from "../../lib/slatejs-edits/replace-text";
1822

1923
export interface CopilotTextareaProps {
2024
className?: string;
@@ -32,6 +36,9 @@ export function CopilotTextarea(props: CopilotTextareaProps): JSX.Element {
3236
};
3337

3438
const valueOnInitialRender = useMemo(() => props.value ?? "", []);
39+
const [lastKnownFullEditorText, setLastKnownFullEditorText] =
40+
useState(valueOnInitialRender);
41+
3542
const initialValue: Descendant[] = useMemo(() => {
3643
return [
3744
{
@@ -97,17 +104,27 @@ export function CopilotTextarea(props: CopilotTextareaProps): JSX.Element {
97104
return makeRenderPlaceholderFunction(placeholderStyleAugmented);
98105
}, [props.placeholderStyle]);
99106

107+
// update the editor text, but only when the value changes from outside the component
108+
useEffect(() => {
109+
if (props.value === lastKnownFullEditorText) {
110+
return;
111+
}
112+
113+
setLastKnownFullEditorText(props.value ?? "");
114+
replaceEditorText(editor, props.value ?? "");
115+
}, [props.value]);
116+
100117
return (
101118
// Add the editable component inside the context.
102119
<Slate
103120
editor={editor}
104121
initialValue={initialValue}
105122
onChange={(value) => {
106-
const newEditorState = getTextAroundCursor(editor)
123+
const newEditorState = getTextAroundCursor(editor);
107124

108125
const fullEditorText = newEditorState
109-
? newEditorState.textBeforeCursor + newEditorState.textAfterCursor
110-
: getFullEditorTextWithNewlines(editor); // we don't double-parse the editor. When `newEditorState` is null, we didn't parse the editor yet.
126+
? newEditorState.textBeforeCursor + newEditorState.textAfterCursor
127+
: getFullEditorTextWithNewlines(editor); // we don't double-parse the editor. When `newEditorState` is null, we didn't parse the editor yet.
111128

112129
setLastKnownFullEditorText(fullEditorText);
113130
onChangeHandlerForAutocomplete(newEditorState);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Editor, Path, Transforms, Node, Element } from "slate";
2+
3+
export function replaceEditorText(editor: Editor, newText: string) {
4+
// clear all previous text
5+
const paths: Path[] = [];
6+
for (const [node, path] of Node.nodes(editor)) {
7+
if (
8+
Element.isElement(node) &&
9+
(node.type === "paragraph" || node.type === "suggestion") &&
10+
path.length === 1
11+
) {
12+
paths.push(path);
13+
}
14+
}
15+
for (const path of paths) {
16+
Transforms.removeNodes(editor, { at: path });
17+
}
18+
19+
// insert new text
20+
Transforms.insertNodes(
21+
editor,
22+
[
23+
{
24+
type: "paragraph",
25+
children: [{ text: newText }],
26+
},
27+
],
28+
{
29+
at: [0],
30+
}
31+
);
32+
}

0 commit comments

Comments
 (0)