@copilotkit/react-textarea 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +133 -0
- package/CHANGELOG.md +50 -0
- package/dist/chunk-4LJAS777.mjs +10 -0
- package/dist/chunk-4LJAS777.mjs.map +1 -0
- package/dist/chunk-5SJM4R4K.mjs +10 -0
- package/dist/chunk-5SJM4R4K.mjs.map +1 -0
- package/dist/chunk-ETGKLURC.mjs +27 -0
- package/dist/chunk-ETGKLURC.mjs.map +1 -0
- package/dist/chunk-GOAPCQCR.mjs +124 -0
- package/dist/chunk-GOAPCQCR.mjs.map +1 -0
- package/dist/chunk-H4VKQGVU.mjs +3 -0
- package/dist/chunk-H4VKQGVU.mjs.map +1 -0
- package/dist/chunk-IU3WTXLQ.mjs +3 -0
- package/dist/chunk-IU3WTXLQ.mjs.map +1 -0
- package/dist/chunk-JD7BAH7U.mjs +3 -0
- package/dist/chunk-JD7BAH7U.mjs.map +1 -0
- package/dist/chunk-JJASB23A.mjs +88 -0
- package/dist/chunk-JJASB23A.mjs.map +1 -0
- package/dist/chunk-KCHYD3EB.mjs +107 -0
- package/dist/chunk-KCHYD3EB.mjs.map +1 -0
- package/dist/chunk-KGKLUWKW.mjs +47 -0
- package/dist/chunk-KGKLUWKW.mjs.map +1 -0
- package/dist/chunk-KKG2RK2T.mjs +16 -0
- package/dist/chunk-KKG2RK2T.mjs.map +1 -0
- package/dist/chunk-L24ZN4LL.mjs +65 -0
- package/dist/chunk-L24ZN4LL.mjs.map +1 -0
- package/dist/chunk-MRXNTQOX.mjs +55 -0
- package/dist/chunk-MRXNTQOX.mjs.map +1 -0
- package/dist/chunk-NKW5OU2S.mjs +33 -0
- package/dist/chunk-NKW5OU2S.mjs.map +1 -0
- package/dist/chunk-RVJAOO4S.mjs +18 -0
- package/dist/chunk-RVJAOO4S.mjs.map +1 -0
- package/dist/chunk-TSF4AJIK.mjs +30 -0
- package/dist/chunk-TSF4AJIK.mjs.map +1 -0
- package/dist/chunk-X4FJ6WVZ.mjs +66 -0
- package/dist/chunk-X4FJ6WVZ.mjs.map +1 -0
- package/dist/chunk-Y5BUWZOI.mjs +37 -0
- package/dist/chunk-Y5BUWZOI.mjs.map +1 -0
- package/dist/chunk-YPBKY4KY.mjs +3 -0
- package/dist/chunk-YPBKY4KY.mjs.map +1 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.d.ts +18 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.mjs +17 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.mjs.map +1 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-element.d.ts +6 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-element.mjs +4 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-element.mjs.map +1 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-placeholder.d.ts +6 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-placeholder.mjs +4 -0
- package/dist/components/copilot-textarea/base-copilot-textarea/render-placeholder.mjs.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.mjs +18 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.mjs +3 -0
- package/dist/context/index.mjs.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/hooks/use-autosuggestions.d.ts +13 -0
- package/dist/hooks/use-autosuggestions.mjs +7 -0
- package/dist/hooks/use-autosuggestions.mjs.map +1 -0
- package/dist/hooks/use-copilot-textarea-editor.d.ts +8 -0
- package/dist/hooks/use-copilot-textarea-editor.mjs +5 -0
- package/dist/hooks/use-copilot-textarea-editor.mjs.map +1 -0
- package/dist/index.css +340 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +21 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/debouncer.d.ts +11 -0
- package/dist/lib/debouncer.mjs +4 -0
- package/dist/lib/debouncer.mjs.map +1 -0
- package/dist/lib/editor-to-text.d.ts +7 -0
- package/dist/lib/editor-to-text.mjs +45 -0
- package/dist/lib/editor-to-text.mjs.map +1 -0
- package/dist/lib/get-text-around-cursor.d.ts +8 -0
- package/dist/lib/get-text-around-cursor.mjs +4 -0
- package/dist/lib/get-text-around-cursor.mjs.map +1 -0
- package/dist/lib/slatejs-edits/add-autocompletions.d.ts +8 -0
- package/dist/lib/slatejs-edits/add-autocompletions.mjs +4 -0
- package/dist/lib/slatejs-edits/add-autocompletions.mjs.map +1 -0
- package/dist/lib/slatejs-edits/clear-autocompletions.d.ts +8 -0
- package/dist/lib/slatejs-edits/clear-autocompletions.mjs +4 -0
- package/dist/lib/slatejs-edits/clear-autocompletions.mjs.map +1 -0
- package/dist/lib/slatejs-edits/replace-text.d.ts +5 -0
- package/dist/lib/slatejs-edits/replace-text.mjs +4 -0
- package/dist/lib/slatejs-edits/replace-text.mjs.map +1 -0
- package/dist/lib/slatejs-edits/with-partial-history.d.ts +10 -0
- package/dist/lib/slatejs-edits/with-partial-history.mjs +4 -0
- package/dist/lib/slatejs-edits/with-partial-history.mjs.map +1 -0
- package/dist/lib/utils.d.ts +10 -0
- package/dist/lib/utils.mjs +4 -0
- package/dist/lib/utils.mjs.map +1 -0
- package/dist/types/autosuggestion-state.d.ts +8 -0
- package/dist/types/autosuggestion-state.mjs +3 -0
- package/dist/types/autosuggestion-state.mjs.map +1 -0
- package/dist/types/autosuggestions-bare-function.d.ts +3 -0
- package/dist/types/autosuggestions-bare-function.mjs +3 -0
- package/dist/types/autosuggestions-bare-function.mjs.map +1 -0
- package/dist/types/base-autosuggestions-config.d.ts +9 -0
- package/dist/types/base-autosuggestions-config.mjs +4 -0
- package/dist/types/base-autosuggestions-config.mjs.map +1 -0
- package/dist/types/custom-editor.d.ts +29 -0
- package/dist/types/custom-editor.mjs +3 -0
- package/dist/types/custom-editor.mjs.map +1 -0
- package/dist/types/editor-autocomplete-state.d.ts +10 -0
- package/dist/types/editor-autocomplete-state.mjs +5 -0
- package/dist/types/editor-autocomplete-state.mjs.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.mjs +5 -0
- package/dist/types/index.mjs.map +1 -0
- package/package.json +53 -0
- package/postcss.config.js +9 -0
- package/src/components/copilot-textarea/base-copilot-textarea/base-copilot-textarea.tsx +175 -0
- package/src/components/copilot-textarea/base-copilot-textarea/render-element.tsx +42 -0
- package/src/components/copilot-textarea/base-copilot-textarea/render-placeholder.tsx +25 -0
- package/src/components/index.ts +2 -0
- package/src/context/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-autosuggestions.ts +129 -0
- package/src/hooks/use-copilot-textarea-editor.tsx +104 -0
- package/src/index.tsx +7 -0
- package/src/lib/debouncer.ts +38 -0
- package/src/lib/editor-to-text.ts +64 -0
- package/src/lib/get-text-around-cursor.ts +82 -0
- package/src/lib/slatejs-edits/add-autocompletions.ts +30 -0
- package/src/lib/slatejs-edits/clear-autocompletions.ts +15 -0
- package/src/lib/slatejs-edits/replace-text.ts +32 -0
- package/src/lib/slatejs-edits/with-partial-history.ts +156 -0
- package/src/lib/utils.ts +59 -0
- package/src/styles.css +3 -0
- package/src/types/autosuggestion-state.ts +6 -0
- package/src/types/autosuggestions-bare-function.ts +5 -0
- package/src/types/base-autosuggestions-config.tsx +15 -0
- package/src/types/custom-editor.tsx +29 -0
- package/src/types/editor-autocomplete-state.ts +19 -0
- package/src/types/index.ts +4 -0
- package/tailwind.config.js +7 -0
- package/tsconfig.json +5 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { BaseEditor, Descendant, Element } from "slate";
|
|
2
|
+
import { HistoryEditor } from "slate-history";
|
|
3
|
+
import { ReactEditor } from "slate-react";
|
|
4
|
+
import { SuggestionAwareText } from "../types/custom-editor";
|
|
5
|
+
|
|
6
|
+
function nodeChildrenToTextComponents(
|
|
7
|
+
editor: BaseEditor & ReactEditor & HistoryEditor,
|
|
8
|
+
nodes: Descendant[]
|
|
9
|
+
): SuggestionAwareText[] {
|
|
10
|
+
// find inlineable elements
|
|
11
|
+
const indeciesOfInlineElements = new Set(
|
|
12
|
+
nodes
|
|
13
|
+
.map((node, index) => {
|
|
14
|
+
if (Element.isElement(node) && editor.isInline(node)) {
|
|
15
|
+
return index;
|
|
16
|
+
}
|
|
17
|
+
return -1;
|
|
18
|
+
})
|
|
19
|
+
.filter((index) => index !== -1)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// ignorable elements = inline elements,
|
|
23
|
+
// or neighbors of inline elements that are {text: ""}
|
|
24
|
+
const nonIgnorableItems = nodes.filter((node, index) => {
|
|
25
|
+
const isInline = indeciesOfInlineElements.has(index);
|
|
26
|
+
if (isInline) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isNeighbourOfInline =
|
|
31
|
+
indeciesOfInlineElements.has(index - 1) ||
|
|
32
|
+
indeciesOfInlineElements.has(index + 1);
|
|
33
|
+
if (isNeighbourOfInline) {
|
|
34
|
+
return (node as any).text !== "";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return true;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return nonIgnorableItems
|
|
41
|
+
.map((node) => {
|
|
42
|
+
if (Element.isElement(node)) {
|
|
43
|
+
switch (node.type) {
|
|
44
|
+
case "paragraph":
|
|
45
|
+
return nodeChildrenToTextComponents(editor, node.children);
|
|
46
|
+
case "suggestion":
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
return [node];
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
.reduce((acc, val) => acc.concat(val), []);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const editorToText = (
|
|
57
|
+
editor: BaseEditor & ReactEditor & HistoryEditor
|
|
58
|
+
) => {
|
|
59
|
+
const flattened = nodeChildrenToTextComponents(editor, editor.children);
|
|
60
|
+
|
|
61
|
+
const text = flattened.map((textComponent) => textComponent.text).join("\n");
|
|
62
|
+
|
|
63
|
+
return text;
|
|
64
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Editor, Node, Path, Range, Text, Element } from "slate";
|
|
2
|
+
import { EditorAutocompleteState } from "../types/editor-autocomplete-state";
|
|
3
|
+
|
|
4
|
+
export function getTextAroundCursor(
|
|
5
|
+
editor: Editor
|
|
6
|
+
): EditorAutocompleteState | null {
|
|
7
|
+
const { selection } = editor;
|
|
8
|
+
|
|
9
|
+
if (!selection || !Range.isCollapsed(selection)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
// Create two ranges: one before the anchor and one after
|
|
13
|
+
const beforeRange: Range = {
|
|
14
|
+
anchor: Editor.start(editor, []),
|
|
15
|
+
focus: selection.anchor,
|
|
16
|
+
};
|
|
17
|
+
const afterRange: Range = {
|
|
18
|
+
anchor: selection.anchor,
|
|
19
|
+
focus: Editor.end(editor, []),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Extract text for these ranges
|
|
23
|
+
const before = extractTextWithNewlines(editor, beforeRange);
|
|
24
|
+
const after = extractTextWithNewlines(editor, afterRange);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
cursorPoint: selection.anchor,
|
|
28
|
+
textBeforeCursor: before,
|
|
29
|
+
textAfterCursor: after,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getFullEditorTextWithNewlines(editor: Editor): string {
|
|
34
|
+
const fullDocumentRange: Range = {
|
|
35
|
+
anchor: Editor.start(editor, []),
|
|
36
|
+
focus: Editor.end(editor, []),
|
|
37
|
+
};
|
|
38
|
+
return extractTextWithNewlines(editor, fullDocumentRange);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Helper function to extract text with newlines
|
|
42
|
+
export function extractTextWithNewlines(editor: Editor, range: Range): string {
|
|
43
|
+
const voids = false;
|
|
44
|
+
const [start, end] = Range.edges(range);
|
|
45
|
+
let text = "";
|
|
46
|
+
let lastBlock: Node | null = null;
|
|
47
|
+
|
|
48
|
+
for (const [node, path] of Editor.nodes(editor, {
|
|
49
|
+
at: range,
|
|
50
|
+
match: Text.isText,
|
|
51
|
+
voids,
|
|
52
|
+
})) {
|
|
53
|
+
let t = node.text;
|
|
54
|
+
|
|
55
|
+
// Determine the parent block of the current text node
|
|
56
|
+
const [block] = Editor.above(editor, {
|
|
57
|
+
at: path,
|
|
58
|
+
match: (n) => Element.isElement(n) && n.type === "paragraph",
|
|
59
|
+
}) || [null];
|
|
60
|
+
|
|
61
|
+
// If we encounter a new block, prepend a newline
|
|
62
|
+
if (lastBlock !== block && block) {
|
|
63
|
+
// check that lastBlock is not null to avoid adding a newline at the beginning
|
|
64
|
+
if (lastBlock) {
|
|
65
|
+
text += "\n";
|
|
66
|
+
}
|
|
67
|
+
lastBlock = block;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (Path.equals(path, end.path)) {
|
|
71
|
+
t = t.slice(0, end.offset);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (Path.equals(path, start.path)) {
|
|
75
|
+
t = t.slice(start.offset);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
text += t;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return text;
|
|
82
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BasePoint, Transforms } from "slate";
|
|
2
|
+
import { CustomEditor } from "../../types/custom-editor";
|
|
3
|
+
|
|
4
|
+
export function addAutocompletionsToEditor(
|
|
5
|
+
editor: CustomEditor,
|
|
6
|
+
newSuggestion: string,
|
|
7
|
+
point: BasePoint
|
|
8
|
+
) {
|
|
9
|
+
const editorPosition = editor.selection;
|
|
10
|
+
|
|
11
|
+
Transforms.insertNodes(
|
|
12
|
+
editor,
|
|
13
|
+
[
|
|
14
|
+
{
|
|
15
|
+
type: "suggestion",
|
|
16
|
+
inline: true,
|
|
17
|
+
content: newSuggestion,
|
|
18
|
+
children: [{ text: "" }],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
{
|
|
22
|
+
at: point,
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// restore cursor position
|
|
27
|
+
if (editorPosition) {
|
|
28
|
+
editor.selection = editorPosition;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Path, Node, Element, Transforms } from "slate";
|
|
2
|
+
import { CustomEditor } from "../../types/custom-editor";
|
|
3
|
+
|
|
4
|
+
export function clearAutocompletionsFromEditor(editor: CustomEditor) {
|
|
5
|
+
// clear previous suggestion
|
|
6
|
+
const paths: Path[] = [];
|
|
7
|
+
for (const [node, path] of Node.nodes(editor)) {
|
|
8
|
+
if (Element.isElement(node) && node.type === "suggestion") {
|
|
9
|
+
paths.push(path);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
for (const path of paths) {
|
|
13
|
+
Transforms.removeNodes(editor, { at: path });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -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,0 +1,156 @@
|
|
|
1
|
+
import { Editor, Operation, Path, Range, Transforms } from "slate";
|
|
2
|
+
import { HistoryEditor } from "slate-history";
|
|
3
|
+
|
|
4
|
+
// Copy-pasted from `https://github.com/ianstormtaylor/slate/blob/main/packages/slate-history/src/with-history.ts`
|
|
5
|
+
// With one exception: the `shouldSave` function is passed in as an argument to `withPartialHistory` instead of being hardcoded
|
|
6
|
+
export type ShouldSaveToHistory = (
|
|
7
|
+
op: Operation,
|
|
8
|
+
prev: Operation | undefined
|
|
9
|
+
) => boolean;
|
|
10
|
+
|
|
11
|
+
export const withPartialHistory = <T extends Editor>(
|
|
12
|
+
editor: T,
|
|
13
|
+
shouldSave: ShouldSaveToHistory
|
|
14
|
+
) => {
|
|
15
|
+
const e = editor as T & HistoryEditor;
|
|
16
|
+
const { apply } = e;
|
|
17
|
+
e.history = { undos: [], redos: [] };
|
|
18
|
+
|
|
19
|
+
e.redo = () => {
|
|
20
|
+
const { history } = e;
|
|
21
|
+
const { redos } = history;
|
|
22
|
+
|
|
23
|
+
if (redos.length > 0) {
|
|
24
|
+
const batch = redos[redos.length - 1];
|
|
25
|
+
|
|
26
|
+
if (batch.selectionBefore) {
|
|
27
|
+
Transforms.setSelection(e, batch.selectionBefore);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
HistoryEditor.withoutSaving(e, () => {
|
|
31
|
+
Editor.withoutNormalizing(e, () => {
|
|
32
|
+
for (const op of batch.operations) {
|
|
33
|
+
e.apply(op);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
history.redos.pop();
|
|
39
|
+
e.writeHistory("undos", batch);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
e.undo = () => {
|
|
44
|
+
const { history } = e;
|
|
45
|
+
const { undos } = history;
|
|
46
|
+
|
|
47
|
+
if (undos.length > 0) {
|
|
48
|
+
const batch = undos[undos.length - 1];
|
|
49
|
+
|
|
50
|
+
HistoryEditor.withoutSaving(e, () => {
|
|
51
|
+
Editor.withoutNormalizing(e, () => {
|
|
52
|
+
const inverseOps = batch.operations.map(Operation.inverse).reverse();
|
|
53
|
+
|
|
54
|
+
for (const op of inverseOps) {
|
|
55
|
+
e.apply(op);
|
|
56
|
+
}
|
|
57
|
+
if (batch.selectionBefore) {
|
|
58
|
+
Transforms.setSelection(e, batch.selectionBefore);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
e.writeHistory("redos", batch);
|
|
64
|
+
history.undos.pop();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
e.apply = (op: Operation) => {
|
|
69
|
+
const { operations, history } = e;
|
|
70
|
+
const { undos } = history;
|
|
71
|
+
const lastBatch = undos[undos.length - 1];
|
|
72
|
+
const lastOp =
|
|
73
|
+
lastBatch && lastBatch.operations[lastBatch.operations.length - 1];
|
|
74
|
+
let save = HistoryEditor.isSaving(e);
|
|
75
|
+
let merge = HistoryEditor.isMerging(e);
|
|
76
|
+
|
|
77
|
+
if (save == null) {
|
|
78
|
+
save = shouldSave(op, lastOp);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (save) {
|
|
82
|
+
if (merge == null) {
|
|
83
|
+
if (lastBatch == null) {
|
|
84
|
+
merge = false;
|
|
85
|
+
} else if (operations.length !== 0) {
|
|
86
|
+
merge = true;
|
|
87
|
+
} else {
|
|
88
|
+
merge = shouldMerge(op, lastOp);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (lastBatch && merge) {
|
|
93
|
+
lastBatch.operations.push(op);
|
|
94
|
+
} else {
|
|
95
|
+
const batch = {
|
|
96
|
+
operations: [op],
|
|
97
|
+
selectionBefore: e.selection,
|
|
98
|
+
};
|
|
99
|
+
e.writeHistory("undos", batch);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
while (undos.length > 100) {
|
|
103
|
+
undos.shift();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
history.redos = [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
apply(op);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
e.writeHistory = (stack: "undos" | "redos", batch: any) => {
|
|
113
|
+
e.history[stack].push(batch);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return e;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check whether to merge an operation into the previous operation.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
const shouldMerge = (op: Operation, prev: Operation | undefined): boolean => {
|
|
124
|
+
if (
|
|
125
|
+
prev &&
|
|
126
|
+
op.type === "insert_text" &&
|
|
127
|
+
prev.type === "insert_text" &&
|
|
128
|
+
op.offset === prev.offset + prev.text.length &&
|
|
129
|
+
Path.equals(op.path, prev.path)
|
|
130
|
+
) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
prev &&
|
|
136
|
+
op.type === "remove_text" &&
|
|
137
|
+
prev.type === "remove_text" &&
|
|
138
|
+
op.offset + op.text.length === prev.offset &&
|
|
139
|
+
Path.equals(op.path, prev.path)
|
|
140
|
+
) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return false;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const defaultShouldSave = (
|
|
148
|
+
op: Operation,
|
|
149
|
+
prev: Operation | undefined
|
|
150
|
+
): boolean => {
|
|
151
|
+
if (op.type === "set_selection") {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return true;
|
|
156
|
+
};
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { customAlphabet } from "nanoid";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
|
|
5
|
+
export function cn(...inputs: ClassValue[]) {
|
|
6
|
+
return twMerge(clsx(inputs));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const nanoid = customAlphabet(
|
|
10
|
+
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
11
|
+
7
|
|
12
|
+
); // 7-character random string
|
|
13
|
+
|
|
14
|
+
export async function fetcher<JSON = any>(
|
|
15
|
+
input: RequestInfo,
|
|
16
|
+
init?: RequestInit
|
|
17
|
+
): Promise<JSON> {
|
|
18
|
+
const res = await fetch(input, init);
|
|
19
|
+
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
const json = await res.json();
|
|
22
|
+
if (json.error) {
|
|
23
|
+
const error = new Error(json.error) as Error & {
|
|
24
|
+
status: number;
|
|
25
|
+
};
|
|
26
|
+
error.status = res.status;
|
|
27
|
+
throw error;
|
|
28
|
+
} else {
|
|
29
|
+
throw new Error("An unexpected error occurred");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return res.json();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function formatDate(input: string | number | Date): string {
|
|
37
|
+
const date = new Date(input);
|
|
38
|
+
return date.toLocaleDateString("en-US", {
|
|
39
|
+
month: "long",
|
|
40
|
+
day: "numeric",
|
|
41
|
+
year: "numeric",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const arraysAreEqual = (arr1: number[], arr2: number[]): boolean =>
|
|
46
|
+
arr1.length === arr2.length &&
|
|
47
|
+
arr1.every((value, index) => value === arr2[index]);
|
|
48
|
+
|
|
49
|
+
export function nullableCompatibleEqualityCheck<T>(
|
|
50
|
+
naiveEqualityCheck: (a: T, b: T) => boolean,
|
|
51
|
+
a: T | null | undefined,
|
|
52
|
+
b: T | null | undefined
|
|
53
|
+
): boolean {
|
|
54
|
+
if (a === null || a === undefined || b === null || b === undefined) {
|
|
55
|
+
return a === b;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return naiveEqualityCheck(a, b);
|
|
59
|
+
}
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface BaseAutosuggestionsConfig {
|
|
2
|
+
purposePrompt: string;
|
|
3
|
+
debounceTime: number;
|
|
4
|
+
acceptAutosuggestionKey: string;
|
|
5
|
+
disableWhenEmpty: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const defaultBaseAutosuggestionsConfig: Omit<
|
|
9
|
+
BaseAutosuggestionsConfig,
|
|
10
|
+
"purposePrompt"
|
|
11
|
+
> = {
|
|
12
|
+
debounceTime: 500,
|
|
13
|
+
acceptAutosuggestionKey: "Tab",
|
|
14
|
+
disableWhenEmpty: true,
|
|
15
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BaseEditor } from "slate";
|
|
2
|
+
import { ReactEditor } from "slate-react";
|
|
3
|
+
import { HistoryEditor } from "slate-history";
|
|
4
|
+
|
|
5
|
+
export type CustomEditor = BaseEditor & ReactEditor & HistoryEditor;
|
|
6
|
+
|
|
7
|
+
export type ParagraphElement = {
|
|
8
|
+
type: "paragraph";
|
|
9
|
+
children: CustomText[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type SuggestionElement = {
|
|
13
|
+
type: "suggestion";
|
|
14
|
+
inline: boolean;
|
|
15
|
+
content: string;
|
|
16
|
+
children: CustomText[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type CustomElement = ParagraphElement | SuggestionElement;
|
|
20
|
+
export type SuggestionAwareText = { text: string };
|
|
21
|
+
export type CustomText = SuggestionAwareText;
|
|
22
|
+
|
|
23
|
+
declare module "slate" {
|
|
24
|
+
interface CustomTypes {
|
|
25
|
+
Editor: CustomEditor;
|
|
26
|
+
Element: CustomElement;
|
|
27
|
+
Text: CustomText;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BasePoint } from "slate";
|
|
2
|
+
import { arraysAreEqual } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
export interface EditorAutocompleteState {
|
|
5
|
+
cursorPoint: BasePoint;
|
|
6
|
+
textBeforeCursor: string;
|
|
7
|
+
textAfterCursor: string;
|
|
8
|
+
}
|
|
9
|
+
export function areEqual_autocompleteState(
|
|
10
|
+
prev: EditorAutocompleteState,
|
|
11
|
+
next: EditorAutocompleteState
|
|
12
|
+
) {
|
|
13
|
+
return (
|
|
14
|
+
prev.cursorPoint.offset === next.cursorPoint.offset &&
|
|
15
|
+
arraysAreEqual(prev.cursorPoint.path, next.cursorPoint.path) &&
|
|
16
|
+
prev.textBeforeCursor === next.textBeforeCursor &&
|
|
17
|
+
prev.textAfterCursor === next.textAfterCursor
|
|
18
|
+
);
|
|
19
|
+
}
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig, Options } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig((options: Options) => ({
|
|
4
|
+
entry: ["src/**/*.{ts,tsx}"],
|
|
5
|
+
format: ["esm"],
|
|
6
|
+
dts: true,
|
|
7
|
+
minify: false,
|
|
8
|
+
clean: true,
|
|
9
|
+
external: ["react"],
|
|
10
|
+
sourcemap: true,
|
|
11
|
+
...options,
|
|
12
|
+
}));
|