@fluentui-copilot/chat-input-plugins 0.0.4 → 0.0.5
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/CHANGELOG.json +34 -1
- package/CHANGELOG.md +14 -2
- package/dist/index.d.ts +2 -0
- package/lib/BasicFunctionality/BasicFunctionality.base.js +21 -14
- package/lib/BasicFunctionality/BasicFunctionality.base.js.map +1 -1
- package/lib/BasicFunctionality/SentinelNode.js +2 -1
- package/lib/BasicFunctionality/SentinelNode.js.map +1 -1
- package/lib/GhostText/GhostText.base.js +46 -4
- package/lib/GhostText/GhostText.base.js.map +1 -1
- package/lib/ImperativeControl/ImperativeControl.base.js +4 -2
- package/lib/ImperativeControl/ImperativeControl.base.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib-commonjs/BasicFunctionality/BasicFunctionality.base.js +18 -12
- package/lib-commonjs/BasicFunctionality/BasicFunctionality.base.js.map +1 -1
- package/lib-commonjs/BasicFunctionality/SentinelNode.js +5 -1
- package/lib-commonjs/BasicFunctionality/SentinelNode.js.map +1 -1
- package/lib-commonjs/GhostText/GhostText.base.js +45 -3
- package/lib-commonjs/GhostText/GhostText.base.js.map +1 -1
- package/lib-commonjs/ImperativeControl/ImperativeControl.base.js +4 -2
- package/lib-commonjs/ImperativeControl/ImperativeControl.base.js.map +1 -1
- package/lib-commonjs/index.js +3 -0
- package/lib-commonjs/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.json
CHANGED
|
@@ -2,7 +2,40 @@
|
|
|
2
2
|
"name": "@fluentui-copilot/chat-input-plugins",
|
|
3
3
|
"entries": [
|
|
4
4
|
{
|
|
5
|
-
"date": "
|
|
5
|
+
"date": "Wed, 08 May 2024 22:09:00 GMT",
|
|
6
|
+
"tag": "@fluentui-copilot/chat-input-plugins_v0.0.5",
|
|
7
|
+
"version": "0.0.5",
|
|
8
|
+
"comments": {
|
|
9
|
+
"patch": [
|
|
10
|
+
{
|
|
11
|
+
"author": "owcampbe@microsoft.com",
|
|
12
|
+
"package": "@fluentui-copilot/chat-input-plugins",
|
|
13
|
+
"commit": "2bcbb68c4751536bb0ee7b4b19ded4f21be9f956",
|
|
14
|
+
"comment": "fix: Strip out sentinel node characters from ImperativeControlPlugin."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"author": "owcampbe@microsoft.com",
|
|
18
|
+
"package": "@fluentui-copilot/chat-input-plugins",
|
|
19
|
+
"commit": "da7a5eaa6af7c4eacef58d70f51acfb30977a38c",
|
|
20
|
+
"comment": "fix: insertTextAtCursor should append if no selection exists."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"author": "owcampbe@microsoft.com",
|
|
24
|
+
"package": "@fluentui-copilot/chat-input-plugins",
|
|
25
|
+
"commit": "8b3fa1f2bc2732776130769a904a98c314207fef",
|
|
26
|
+
"comment": "fix: Improve navigation around GhostText."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"author": "owcampbe@microsoft.com",
|
|
30
|
+
"package": "@fluentui-copilot/chat-input-plugins",
|
|
31
|
+
"commit": "8758b361c9586e347ec3c8c16038532598c31e77",
|
|
32
|
+
"comment": "fix: Get fresh reference to lexical node for update closure."
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"date": "Tue, 30 Apr 2024 20:55:42 GMT",
|
|
6
39
|
"tag": "@fluentui-copilot/chat-input-plugins_v0.0.4",
|
|
7
40
|
"version": "0.0.4",
|
|
8
41
|
"comments": {
|
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
# Change Log - @fluentui-copilot/chat-input-plugins
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Wed, 08 May 2024 22:09:00 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## [0.0.5](https://github.com/microsoft/fluentai/tree/@fluentui-copilot/chat-input-plugins_v0.0.5)
|
|
8
|
+
|
|
9
|
+
Wed, 08 May 2024 22:09:00 GMT
|
|
10
|
+
[Compare changes](https://github.com/microsoft/fluentai/compare/@fluentui-copilot/chat-input-plugins_v0.0.4..@fluentui-copilot/chat-input-plugins_v0.0.5)
|
|
11
|
+
|
|
12
|
+
### Patches
|
|
13
|
+
|
|
14
|
+
- fix: Strip out sentinel node characters from ImperativeControlPlugin. ([PR #1612](https://github.com/microsoft/fluentai/pull/1612) by owcampbe@microsoft.com)
|
|
15
|
+
- fix: insertTextAtCursor should append if no selection exists. ([PR #1590](https://github.com/microsoft/fluentai/pull/1590) by owcampbe@microsoft.com)
|
|
16
|
+
- fix: Improve navigation around GhostText. ([PR #1611](https://github.com/microsoft/fluentai/pull/1611) by owcampbe@microsoft.com)
|
|
17
|
+
- fix: Get fresh reference to lexical node for update closure. ([PR #1613](https://github.com/microsoft/fluentai/pull/1613) by owcampbe@microsoft.com)
|
|
18
|
+
|
|
7
19
|
## [0.0.4](https://github.com/microsoft/fluentai/tree/@fluentui-copilot/chat-input-plugins_v0.0.4)
|
|
8
20
|
|
|
9
|
-
Tue, 30 Apr 2024 20:
|
|
21
|
+
Tue, 30 Apr 2024 20:55:42 GMT
|
|
10
22
|
[Compare changes](https://github.com/microsoft/fluentai/compare/@fluentui-copilot/chat-input-plugins_v0.0.3..@fluentui-copilot/chat-input-plugins_v0.0.4)
|
|
11
23
|
|
|
12
24
|
### Patches
|
package/dist/index.d.ts
CHANGED
|
@@ -150,6 +150,8 @@ export declare class ManualGhostTextBase<ComponentPropsType> {
|
|
|
150
150
|
cancelGhostText(): void;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
export declare const SENTINEL_VALUE = "\u200B\u200C";
|
|
154
|
+
|
|
153
155
|
export declare class SentinelNode extends TextNode {
|
|
154
156
|
constructor(key?: NodeKey);
|
|
155
157
|
static getType(): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _ as _define_property } from "@swc/helpers/_/_define_property";
|
|
2
|
-
import { $createTextNode, $getLeafNodes, $getRoot, $getSelection, $insertNodes, $isRangeSelection, $normalizeSelection__EXPERIMENTAL, $setSelection, COMMAND_PRIORITY_CRITICAL, INSERT_PARAGRAPH_COMMAND, KEY_ENTER_COMMAND, PASTE_COMMAND, mergeRegister } from '@fluentui-copilot/text-editor';
|
|
3
|
-
import { $createSentinelNode, $isSentinelNode, SentinelNode } from './SentinelNode';
|
|
2
|
+
import { $createTextNode, $getLeafNodes, $getNodeByKey, $getRoot, $getSelection, $insertNodes, $isRangeSelection, $normalizeSelection__EXPERIMENTAL, $setSelection, COMMAND_PRIORITY_CRITICAL, INSERT_PARAGRAPH_COMMAND, KEY_ENTER_COMMAND, PASTE_COMMAND, mergeRegister } from '@fluentui-copilot/text-editor';
|
|
3
|
+
import { $createSentinelNode, $isSentinelNode, SENTINEL_VALUE, SentinelNode } from './SentinelNode';
|
|
4
4
|
export class BasicFunctionalityBase {
|
|
5
5
|
__enterHandler(event) {
|
|
6
6
|
const selection = $getSelection();
|
|
@@ -28,7 +28,7 @@ export class BasicFunctionalityBase {
|
|
|
28
28
|
this.deactivateContentCallbacks();
|
|
29
29
|
this.__contentChangeCleanup = this.__editor.registerTextContentListener(text => {
|
|
30
30
|
// Remove the sentinel node
|
|
31
|
-
const processed = text.replace(
|
|
31
|
+
const processed = text.replace(SENTINEL_VALUE, '');
|
|
32
32
|
onContentChange === null || onContentChange === void 0 ? void 0 : onContentChange(processed);
|
|
33
33
|
onCountChanged === null || onCountChanged === void 0 ? void 0 : onCountChanged(processed.length);
|
|
34
34
|
});
|
|
@@ -107,19 +107,23 @@ export class BasicFunctionalityBase {
|
|
|
107
107
|
if (leaves.length === 0) {
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
const
|
|
110
|
+
const lastNode = leaves[leaves.length - 1];
|
|
111
|
+
const lastNodeKey = lastNode.getKey();
|
|
111
112
|
// If the last node isn't a sentinel, add one
|
|
112
|
-
if (!$isSentinelNode(
|
|
113
|
+
if (!$isSentinelNode(lastNode)) {
|
|
113
114
|
this.__editor.update(() => {
|
|
114
|
-
|
|
115
|
+
var
|
|
116
|
+
// We find the node by its key again in case the node was removed before this update runs
|
|
117
|
+
_$getNodeByKey;
|
|
118
|
+
(_$getNodeByKey = $getNodeByKey(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.insertAfter($createSentinelNode());
|
|
115
119
|
}, {
|
|
116
120
|
discrete: true
|
|
117
121
|
});
|
|
118
122
|
return;
|
|
119
123
|
}
|
|
120
124
|
// If the sentinel node is not selected, we're done
|
|
121
|
-
const previous =
|
|
122
|
-
if (!previous || !
|
|
125
|
+
const previous = lastNode.getPreviousSibling();
|
|
126
|
+
if (!previous || !lastNode.isSelected()) {
|
|
123
127
|
return;
|
|
124
128
|
}
|
|
125
129
|
const selection = $getSelection();
|
|
@@ -131,7 +135,8 @@ export class BasicFunctionalityBase {
|
|
|
131
135
|
// where selection is ill-defined.
|
|
132
136
|
if (selection.isCollapsed() && selection.anchor.offset > 0) {
|
|
133
137
|
this.__editor.update(() => {
|
|
134
|
-
|
|
138
|
+
var _$getNodeByKey;
|
|
139
|
+
(_$getNodeByKey = $getNodeByKey(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.selectStart();
|
|
135
140
|
}, {
|
|
136
141
|
discrete: true
|
|
137
142
|
});
|
|
@@ -141,17 +146,19 @@ export class BasicFunctionalityBase {
|
|
|
141
146
|
if (!selection.isCollapsed()) {
|
|
142
147
|
let selectionChanged = false;
|
|
143
148
|
const newSelection = selection.clone();
|
|
144
|
-
if (newSelection.anchor.getNode() ===
|
|
145
|
-
newSelection.anchor.set(
|
|
149
|
+
if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {
|
|
150
|
+
newSelection.anchor.set(lastNodeKey, 0, 'text');
|
|
146
151
|
selectionChanged = true;
|
|
147
152
|
}
|
|
148
|
-
if (newSelection.focus.getNode() ===
|
|
149
|
-
newSelection.focus.set(
|
|
153
|
+
if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {
|
|
154
|
+
newSelection.focus.set(lastNodeKey, 0, 'text');
|
|
150
155
|
selectionChanged = true;
|
|
151
156
|
}
|
|
152
157
|
if (selectionChanged) {
|
|
153
158
|
this.__editor.update(() => {
|
|
154
|
-
|
|
159
|
+
if ($getNodeByKey(lastNodeKey) !== null) {
|
|
160
|
+
$setSelection($normalizeSelection__EXPERIMENTAL(newSelection));
|
|
161
|
+
}
|
|
155
162
|
}, {
|
|
156
163
|
discrete: true
|
|
157
164
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["BasicFunctionality.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getLeafNodes,\n $getRoot,\n $getSelection,\n $insertNodes,\n $isRangeSelection,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n COMMAND_PRIORITY_CRITICAL,\n INSERT_PARAGRAPH_COMMAND,\n KEY_ENTER_COMMAND,\n PASTE_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { $createSentinelNode, $isSentinelNode, SentinelNode } from './SentinelNode';\n\nexport interface IBasicFunctionalityBase {\n insertDefaultValue: (defaultValue: string) => void;\n setIsDisabled: (isDisabled: boolean) => void;\n activateContentCallbacks(onContentChange?: (value: string) => void, onCountChanged?: (count: number) => void): void;\n deactivateContentCallbacks(): void;\n activateTrimWhitespace(): void;\n deactivateTrimWhitespace(): void;\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void): void;\n deactivatePasteCallback(): void;\n cleanup(): void;\n}\n\nexport class BasicFunctionalityBase implements IBasicFunctionalityBase {\n private __editor: LexicalEditor;\n private __contentChangeCleanup?: () => void;\n private __trimWhitespaceCleanup?: () => void;\n private __baseHandlersCleanup?: () => void;\n private __pasteHandlerCleanup?: () => void;\n\n private __enterHandler(event: KeyboardEvent) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return false;\n }\n\n if (event === null) {\n return false;\n }\n\n event.preventDefault();\n\n if (event.shiftKey) {\n return this.__editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);\n }\n\n // Mark event handled to override default behavior\n return true;\n }\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n\n this.__baseHandlersCleanup = mergeRegister(\n this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_CRITICAL),\n\n // Add a sentinel node at the end of the input when there is content.\n // This sentinel node fixes a number of issues.\n // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends\n // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487\n // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.\n // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input\n // mitigates these issues.\n this.__editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const leaves = $getLeafNodes($getRoot());\n if (leaves.length === 0) {\n return;\n }\n\n const sentinel = leaves[leaves.length - 1];\n\n // If the last node isn't a sentinel, add one\n if (!$isSentinelNode(sentinel)) {\n this.__editor.update(\n () => {\n sentinel.insertAfter($createSentinelNode());\n },\n { discrete: true },\n );\n return;\n }\n\n // If the sentinel node is not selected, we're done\n const previous = sentinel.getPreviousSibling();\n if (!previous || !sentinel.isSelected()) {\n return;\n }\n\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return;\n }\n\n // If the cursor is inside the sentinel node, move it out (next to the beginning)\n // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node\n // where selection is ill-defined.\n if (selection.isCollapsed() && selection.anchor.offset > 0) {\n this.__editor.update(\n () => {\n sentinel.selectStart();\n },\n { discrete: true },\n );\n return;\n }\n\n // If the selection is a range which includes the sentinel, modify the range to exclude it\n if (!selection.isCollapsed()) {\n let selectionChanged = false;\n const newSelection = selection.clone();\n\n if (newSelection.anchor.getNode() === sentinel && newSelection.anchor.offset > 0) {\n newSelection.anchor.set(sentinel.getKey(), 0, 'text');\n selectionChanged = true;\n }\n if (newSelection.focus.getNode() === sentinel && newSelection.focus.offset > 0) {\n newSelection.focus.set(sentinel.getKey(), 0, 'text');\n selectionChanged = true;\n }\n\n if (selectionChanged) {\n this.__editor.update(\n () => {\n $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));\n },\n { discrete: true },\n );\n }\n }\n });\n }),\n this.__editor.registerNodeTransform(SentinelNode, node => {\n if (!node.getPreviousSibling() || node.getNextSibling()) {\n node.remove();\n return;\n }\n }),\n );\n }\n\n insertDefaultValue(defaultValue: string) {\n if (defaultValue) {\n this.__editor.update(() => {\n $insertNodes([$createTextNode(defaultValue)]);\n });\n }\n }\n\n activateContentCallbacks(\n onContentChange?: ((value: string) => void) | undefined,\n onCountChanged?: ((count: number) => void) | undefined,\n ) {\n this.deactivateContentCallbacks();\n this.__contentChangeCleanup = this.__editor.registerTextContentListener(text => {\n // Remove the sentinel node\n const processed = text.replace('\\u200b\\u200c', '');\n onContentChange?.(processed);\n onCountChanged?.(processed.length);\n });\n }\n\n deactivateContentCallbacks() {\n this.__contentChangeCleanup?.();\n this.__contentChangeCleanup = undefined;\n }\n\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void) {\n this.__pasteHandlerCleanup = this.__editor.registerCommand(\n PASTE_COMMAND,\n (event: ClipboardEvent) => {\n onPaste(event);\n\n if (event.defaultPrevented) {\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n }\n\n deactivatePasteCallback() {\n this.__pasteHandlerCleanup?.();\n this.__pasteHandlerCleanup = undefined;\n }\n\n activateTrimWhitespace() {\n this.deactivateTrimWhitespace();\n this.__trimWhitespaceCleanup = this.__editor.registerTextContentListener(text => {\n if (text.trim() === '') {\n this.__editor.update(\n () => {\n $getRoot()\n .getAllTextNodes()\n .forEach(node => {\n node.remove();\n });\n },\n // Don't allow undoing this action\n { tag: 'historic' },\n );\n }\n });\n }\n\n deactivateTrimWhitespace() {\n this.__trimWhitespaceCleanup?.();\n this.__trimWhitespaceCleanup = undefined;\n }\n\n setIsDisabled(isDisabled: boolean) {\n this.__editor.setEditable(!isDisabled);\n }\n\n cleanup() {\n this.deactivateContentCallbacks();\n this.deactivateTrimWhitespace();\n this.deactivatePasteCallback();\n this.__baseHandlersCleanup?.();\n this.__baseHandlersCleanup = undefined;\n }\n}\n"],"names":["$createTextNode","$getLeafNodes","$getRoot","$getSelection","$insertNodes","$isRangeSelection","$normalizeSelection__EXPERIMENTAL","$setSelection","COMMAND_PRIORITY_CRITICAL","INSERT_PARAGRAPH_COMMAND","KEY_ENTER_COMMAND","PASTE_COMMAND","mergeRegister","$createSentinelNode","$isSentinelNode","SentinelNode","BasicFunctionalityBase","__enterHandler","event","selection","preventDefault","shiftKey","__editor","dispatchCommand","undefined","insertDefaultValue","defaultValue","update","activateContentCallbacks","onContentChange","onCountChanged","deactivateContentCallbacks","__contentChangeCleanup","registerTextContentListener","text","processed","replace","length","activatePasteCallback","onPaste","__pasteHandlerCleanup","registerCommand","defaultPrevented","deactivatePasteCallback","activateTrimWhitespace","deactivateTrimWhitespace","__trimWhitespaceCleanup","trim","getAllTextNodes","forEach","node","remove","tag","setIsDisabled","isDisabled","setEditable","cleanup","__baseHandlersCleanup","constructor","editor","bind","registerUpdateListener","editorState","read","leaves","sentinel","insertAfter","discrete","previous","getPreviousSibling","isSelected","isCollapsed","anchor","offset","selectStart","selectionChanged","newSelection","clone","getNode","set","getKey","focus","registerNodeTransform","getNextSibling"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["BasicFunctionality.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getLeafNodes,\n $getNodeByKey,\n $getRoot,\n $getSelection,\n $insertNodes,\n $isRangeSelection,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n COMMAND_PRIORITY_CRITICAL,\n INSERT_PARAGRAPH_COMMAND,\n KEY_ENTER_COMMAND,\n PASTE_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { $createSentinelNode, $isSentinelNode, SENTINEL_VALUE, SentinelNode } from './SentinelNode';\n\nexport interface IBasicFunctionalityBase {\n insertDefaultValue: (defaultValue: string) => void;\n setIsDisabled: (isDisabled: boolean) => void;\n activateContentCallbacks(onContentChange?: (value: string) => void, onCountChanged?: (count: number) => void): void;\n deactivateContentCallbacks(): void;\n activateTrimWhitespace(): void;\n deactivateTrimWhitespace(): void;\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void): void;\n deactivatePasteCallback(): void;\n cleanup(): void;\n}\n\nexport class BasicFunctionalityBase implements IBasicFunctionalityBase {\n private __editor: LexicalEditor;\n private __contentChangeCleanup?: () => void;\n private __trimWhitespaceCleanup?: () => void;\n private __baseHandlersCleanup?: () => void;\n private __pasteHandlerCleanup?: () => void;\n\n private __enterHandler(event: KeyboardEvent) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return false;\n }\n\n if (event === null) {\n return false;\n }\n\n event.preventDefault();\n\n if (event.shiftKey) {\n return this.__editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);\n }\n\n // Mark event handled to override default behavior\n return true;\n }\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n\n this.__baseHandlersCleanup = mergeRegister(\n this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_CRITICAL),\n\n // Add a sentinel node at the end of the input when there is content.\n // This sentinel node fixes a number of issues.\n // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends\n // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487\n // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.\n // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input\n // mitigates these issues.\n this.__editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const leaves = $getLeafNodes($getRoot());\n if (leaves.length === 0) {\n return;\n }\n\n const lastNode = leaves[leaves.length - 1];\n const lastNodeKey = lastNode.getKey();\n\n // If the last node isn't a sentinel, add one\n if (!$isSentinelNode(lastNode)) {\n this.__editor.update(\n () => {\n // We find the node by its key again in case the node was removed before this update runs\n $getNodeByKey(lastNodeKey)?.insertAfter($createSentinelNode());\n },\n { discrete: true },\n );\n return;\n }\n\n // If the sentinel node is not selected, we're done\n const previous = lastNode.getPreviousSibling();\n if (!previous || !lastNode.isSelected()) {\n return;\n }\n\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return;\n }\n\n // If the cursor is inside the sentinel node, move it out (next to the beginning)\n // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node\n // where selection is ill-defined.\n if (selection.isCollapsed() && selection.anchor.offset > 0) {\n this.__editor.update(\n () => {\n $getNodeByKey(lastNodeKey)?.selectStart();\n },\n { discrete: true },\n );\n return;\n }\n\n // If the selection is a range which includes the sentinel, modify the range to exclude it\n if (!selection.isCollapsed()) {\n let selectionChanged = false;\n const newSelection = selection.clone();\n\n if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {\n newSelection.anchor.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {\n newSelection.focus.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n\n if (selectionChanged) {\n this.__editor.update(\n () => {\n if ($getNodeByKey(lastNodeKey) !== null) {\n $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));\n }\n },\n { discrete: true },\n );\n }\n }\n });\n }),\n this.__editor.registerNodeTransform(SentinelNode, node => {\n if (!node.getPreviousSibling() || node.getNextSibling()) {\n node.remove();\n return;\n }\n }),\n );\n }\n\n insertDefaultValue(defaultValue: string) {\n if (defaultValue) {\n this.__editor.update(() => {\n $insertNodes([$createTextNode(defaultValue)]);\n });\n }\n }\n\n activateContentCallbacks(\n onContentChange?: ((value: string) => void) | undefined,\n onCountChanged?: ((count: number) => void) | undefined,\n ) {\n this.deactivateContentCallbacks();\n this.__contentChangeCleanup = this.__editor.registerTextContentListener(text => {\n // Remove the sentinel node\n const processed = text.replace(SENTINEL_VALUE, '');\n onContentChange?.(processed);\n onCountChanged?.(processed.length);\n });\n }\n\n deactivateContentCallbacks() {\n this.__contentChangeCleanup?.();\n this.__contentChangeCleanup = undefined;\n }\n\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void) {\n this.__pasteHandlerCleanup = this.__editor.registerCommand(\n PASTE_COMMAND,\n (event: ClipboardEvent) => {\n onPaste(event);\n\n if (event.defaultPrevented) {\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n }\n\n deactivatePasteCallback() {\n this.__pasteHandlerCleanup?.();\n this.__pasteHandlerCleanup = undefined;\n }\n\n activateTrimWhitespace() {\n this.deactivateTrimWhitespace();\n this.__trimWhitespaceCleanup = this.__editor.registerTextContentListener(text => {\n if (text.trim() === '') {\n this.__editor.update(\n () => {\n $getRoot()\n .getAllTextNodes()\n .forEach(node => {\n node.remove();\n });\n },\n // Don't allow undoing this action\n { tag: 'historic' },\n );\n }\n });\n }\n\n deactivateTrimWhitespace() {\n this.__trimWhitespaceCleanup?.();\n this.__trimWhitespaceCleanup = undefined;\n }\n\n setIsDisabled(isDisabled: boolean) {\n this.__editor.setEditable(!isDisabled);\n }\n\n cleanup() {\n this.deactivateContentCallbacks();\n this.deactivateTrimWhitespace();\n this.deactivatePasteCallback();\n this.__baseHandlersCleanup?.();\n this.__baseHandlersCleanup = undefined;\n }\n}\n"],"names":["$createTextNode","$getLeafNodes","$getNodeByKey","$getRoot","$getSelection","$insertNodes","$isRangeSelection","$normalizeSelection__EXPERIMENTAL","$setSelection","COMMAND_PRIORITY_CRITICAL","INSERT_PARAGRAPH_COMMAND","KEY_ENTER_COMMAND","PASTE_COMMAND","mergeRegister","$createSentinelNode","$isSentinelNode","SENTINEL_VALUE","SentinelNode","BasicFunctionalityBase","__enterHandler","event","selection","preventDefault","shiftKey","__editor","dispatchCommand","undefined","insertDefaultValue","defaultValue","update","activateContentCallbacks","onContentChange","onCountChanged","deactivateContentCallbacks","__contentChangeCleanup","registerTextContentListener","text","processed","replace","length","activatePasteCallback","onPaste","__pasteHandlerCleanup","registerCommand","defaultPrevented","deactivatePasteCallback","activateTrimWhitespace","deactivateTrimWhitespace","__trimWhitespaceCleanup","trim","getAllTextNodes","forEach","node","remove","tag","setIsDisabled","isDisabled","setEditable","cleanup","__baseHandlersCleanup","constructor","editor","bind","registerUpdateListener","editorState","read","leaves","lastNode","lastNodeKey","getKey","insertAfter","discrete","previous","getPreviousSibling","isSelected","isCollapsed","anchor","offset","selectStart","selectionChanged","newSelection","clone","getNode","set","focus","registerNodeTransform","getNextSibling"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TextNode } from '@fluentui-copilot/text-editor';
|
|
2
|
+
export const SENTINEL_VALUE = '\u200b\u200c';
|
|
2
3
|
export class SentinelNode extends TextNode {
|
|
3
4
|
static getType() {
|
|
4
5
|
return 'sentinel';
|
|
@@ -12,7 +13,7 @@ export class SentinelNode extends TextNode {
|
|
|
12
13
|
constructor(key) {
|
|
13
14
|
// 2 zero width characters will not be visible in the editor.
|
|
14
15
|
// These also happen to be markers for CIQ to ignore the following content.
|
|
15
|
-
super(
|
|
16
|
+
super(SENTINEL_VALUE, key);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
export function $createSentinelNode(key) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["SentinelNode.ts"],"sourcesContent":["import type { LexicalNode, NodeKey } from '@fluentui-copilot/text-editor';\nimport { TextNode } from '@fluentui-copilot/text-editor';\n\nexport class SentinelNode extends TextNode {\n constructor(key?: NodeKey) {\n // 2 zero width characters will not be visible in the editor.\n // These also happen to be markers for CIQ to ignore the following content.\n super(
|
|
1
|
+
{"version":3,"sources":["SentinelNode.ts"],"sourcesContent":["import type { LexicalNode, NodeKey } from '@fluentui-copilot/text-editor';\nimport { TextNode } from '@fluentui-copilot/text-editor';\n\nexport const SENTINEL_VALUE = '\\u200b\\u200c';\n\nexport class SentinelNode extends TextNode {\n constructor(key?: NodeKey) {\n // 2 zero width characters will not be visible in the editor.\n // These also happen to be markers for CIQ to ignore the following content.\n super(SENTINEL_VALUE, key);\n }\n\n static getType() {\n return 'sentinel';\n }\n static clone(node: SentinelNode) {\n return new SentinelNode(node.__key);\n }\n\n isToken() {\n return true;\n }\n}\n\nexport function $createSentinelNode(key?: NodeKey) {\n return new SentinelNode(key);\n}\n\nexport function $isSentinelNode(node: LexicalNode | null): node is SentinelNode {\n return node instanceof SentinelNode;\n}\n"],"names":["TextNode","SENTINEL_VALUE","SentinelNode","getType","clone","node","__key","isToken","constructor","key","$createSentinelNode","$isSentinelNode"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;","mappings":"AACA,SAASA,QAAQ,QAAQ,gCAAgC;AAEzD,OAAO,MAAMC,iBAAiB,eAAe;AAE7C,OAAO,MAAMC,qBAAqBF;IAOhC,OAAOG,UAAU;QACf,OAAO;IACT;IACA,OAAOC,MAAMC,IAAkB,EAAE;QAC/B,OAAO,IAAIH,aAAaG,KAAKC,KAAK;IACpC;IAEAC,UAAU;QACR,OAAO;IACT;IAfAC,YAAYC,GAAa,CAAE;QACzB,6DAA6D;QAC7D,2EAA2E;QAC3E,KAAK,CAACR,gBAAgBQ;IACxB;AAYF;AAEA,OAAO,SAASC,oBAAoBD,GAAa;IAC/C,OAAO,IAAIP,aAAaO;AAC1B;AAEA,OAAO,SAASE,gBAAgBN,IAAwB;IACtD,OAAOA,gBAAgBH;AACzB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _ as _define_property } from "@swc/helpers/_/_define_property";
|
|
2
|
-
import { $createTextNode, $getNodeByKey, $getSelection, $setSelection, COMMAND_PRIORITY_LOW, KEY_TAB_COMMAND, mergeRegister } from '@fluentui-copilot/text-editor';
|
|
2
|
+
import { $createTextNode, $getNodeByKey, $getSelection, $isRangeSelection, $isTextNode, $setSelection, COMMAND_PRIORITY_LOW, KEY_TAB_COMMAND, mergeRegister } from '@fluentui-copilot/text-editor';
|
|
3
|
+
import { $isSentinelNode } from '../BasicFunctionality';
|
|
3
4
|
export class GhostTextPluginBase {
|
|
4
5
|
cleanup() {
|
|
5
6
|
var _this___cleanup, _this;
|
|
@@ -34,12 +35,14 @@ export class GhostTextPluginBase {
|
|
|
34
35
|
let ghostTextNodeKey = null;
|
|
35
36
|
let lastText = undefined;
|
|
36
37
|
let justRemovedGhostText = false;
|
|
38
|
+
let justAddedGhostText = false;
|
|
37
39
|
function $clearGhostText() {
|
|
38
40
|
const ghostTextNode = ghostTextNodeKey !== null ? $getNodeByKey(ghostTextNodeKey) : null;
|
|
39
41
|
ghostTextNodeKey = null;
|
|
40
42
|
lastText = undefined;
|
|
41
43
|
if (ghostTextNode && ghostTextNode.isAttached()) {
|
|
42
44
|
ghostTextNode.remove();
|
|
45
|
+
justRemovedGhostText = true;
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
function handleGhostTextNodeTransform(node) {
|
|
@@ -58,7 +61,6 @@ export class GhostTextPluginBase {
|
|
|
58
61
|
$clearGhostText();
|
|
59
62
|
const selection = $getSelection();
|
|
60
63
|
if (!text || !selection) {
|
|
61
|
-
justRemovedGhostText = true;
|
|
62
64
|
return;
|
|
63
65
|
}
|
|
64
66
|
const selectionCopy = selection.clone();
|
|
@@ -67,6 +69,8 @@ export class GhostTextPluginBase {
|
|
|
67
69
|
lastText = text;
|
|
68
70
|
selection.insertNodes([node]);
|
|
69
71
|
$setSelection(selectionCopy);
|
|
72
|
+
justAddedGhostText = true;
|
|
73
|
+
justRemovedGhostText = false;
|
|
70
74
|
}, {
|
|
71
75
|
tag: 'historic'
|
|
72
76
|
});
|
|
@@ -76,12 +80,50 @@ export class GhostTextPluginBase {
|
|
|
76
80
|
editorState,
|
|
77
81
|
prevEditorState
|
|
78
82
|
} = props;
|
|
79
|
-
// If this update was caused by
|
|
80
|
-
if (justRemovedGhostText) {
|
|
83
|
+
// If this update was caused by adding or deleting ghost text, don't recheck the ghost text function until a subsequent update
|
|
84
|
+
if (justRemovedGhostText || justAddedGhostText) {
|
|
81
85
|
justRemovedGhostText = false;
|
|
86
|
+
justAddedGhostText = false;
|
|
82
87
|
return;
|
|
83
88
|
}
|
|
84
89
|
editorState.read(() => {
|
|
90
|
+
// We only update the ghost text if the user selection is inside the input
|
|
91
|
+
const selection = $getSelection();
|
|
92
|
+
if (!$getSelection()) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if ($isRangeSelection(selection) && selection.isCollapsed()) {
|
|
96
|
+
var _selectedNode_getPreviousSibling;
|
|
97
|
+
var _selection_getNodes_at;
|
|
98
|
+
// All the `$isXNode` functions prefer `null` over `undefined`
|
|
99
|
+
const selectedNode = (_selection_getNodes_at = selection.getNodes().at(0)) !== null && _selection_getNodes_at !== void 0 ? _selection_getNodes_at : null;
|
|
100
|
+
const previousNodeKey = selectedNode === null || selectedNode === void 0 ? void 0 : (_selectedNode_getPreviousSibling = selectedNode.getPreviousSibling()) === null || _selectedNode_getPreviousSibling === void 0 ? void 0 : _selectedNode_getPreviousSibling.getKey();
|
|
101
|
+
const previousNodeIsGhostText = previousNodeKey === ghostTextNodeKey;
|
|
102
|
+
// If the ghost text is active and we're navigating past it, act as if the ghost text is not there and move 1 extra character
|
|
103
|
+
if (previousNodeIsGhostText && !$isSentinelNode(selectedNode) && $isTextNode(selectedNode) && selection.anchor.offset === 0) {
|
|
104
|
+
editor.update(() => {
|
|
105
|
+
const selection = $getSelection();
|
|
106
|
+
if ($isRangeSelection(selection)) {
|
|
107
|
+
selection.modify('move', false, 'character');
|
|
108
|
+
}
|
|
109
|
+
$clearGhostText();
|
|
110
|
+
}, {
|
|
111
|
+
tag: 'historic'
|
|
112
|
+
});
|
|
113
|
+
// Defer checking the ghost text until after this update has finished
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// If the ghost text is the last node before the sentinel, we shouldn't let selection get past it
|
|
117
|
+
if (previousNodeIsGhostText && $isSentinelNode(selectedNode)) {
|
|
118
|
+
editor.update(() => {
|
|
119
|
+
var _$getNodeByKey_getPreviousSibling, _$getNodeByKey;
|
|
120
|
+
(_$getNodeByKey = $getNodeByKey(previousNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : (_$getNodeByKey_getPreviousSibling = _$getNodeByKey.getPreviousSibling()) === null || _$getNodeByKey_getPreviousSibling === void 0 ? void 0 : _$getNodeByKey_getPreviousSibling.selectEnd();
|
|
121
|
+
}, {
|
|
122
|
+
tag: 'historic'
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
85
127
|
const promise = this.__$getGhostText(editor, editorState, prevEditorState);
|
|
86
128
|
promise.then(handleGhostTextResponse).catch(e => console.error(e));
|
|
87
129
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["GhostText.base.ts"],"sourcesContent":["import type { EditorState, Klass, LexicalEditor, LexicalNode, UpdateListener } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getNodeByKey,\n $getSelection,\n $setSelection,\n COMMAND_PRIORITY_LOW,\n KEY_TAB_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\n\nexport type GetGhostTextFunction = (\n editor: LexicalEditor,\n editorState: EditorState,\n prevEditorState: EditorState,\n) => Promise<string | undefined>;\n\nexport interface IGhostTextNode<ComponentPropsType> extends LexicalNode {\n __content: string;\n __id: string;\n __allowCommitting?: boolean;\n __componentProps?: ComponentPropsType;\n __exposeText?: boolean;\n}\n\nexport class GhostTextPluginBase<ComponentPropsType> {\n private __id: string;\n private __$getGhostText: GetGhostTextFunction;\n private __componentProps?: ComponentPropsType;\n private __exposeText?: boolean;\n private __allowCompletion?: boolean;\n\n private __cleanup?: () => void;\n\n cleanup() {\n this.__cleanup?.();\n }\n\n constructor(\n editor: LexicalEditor,\n id: string,\n $getGhostText: GetGhostTextFunction,\n nodeClass: Klass<IGhostTextNode<ComponentPropsType>>,\n createNode: (\n id: string,\n content: string,\n exposeText?: boolean,\n componentProps?: ComponentPropsType,\n ) => IGhostTextNode<ComponentPropsType>,\n componentProps?: ComponentPropsType,\n // Whether or not the ghost text should count as text inside the input for submitting and character count\n exposeText?: boolean,\n allowCompletion?: boolean,\n ) {\n this.__id = id;\n this.__$getGhostText = $getGhostText;\n this.__componentProps = componentProps;\n this.__exposeText = exposeText;\n this.__allowCompletion = allowCompletion;\n\n let ghostTextNodeKey: string | null = null;\n let lastText: string | undefined = undefined;\n let justRemovedGhostText = false;\n\n function $clearGhostText() {\n const ghostTextNode = ghostTextNodeKey !== null ? $getNodeByKey(ghostTextNodeKey) : null;\n ghostTextNodeKey = null;\n lastText = undefined;\n if (ghostTextNode && ghostTextNode.isAttached()) {\n ghostTextNode.remove();\n }\n }\n function handleGhostTextNodeTransform(node: IGhostTextNode<ComponentPropsType>) {\n const key = node.getKey();\n\n if (node.__id === id && key !== ghostTextNodeKey) {\n // Only one ghost text\n node.remove();\n $clearGhostText();\n }\n }\n\n const handleGhostTextResponse = (text?: string) => {\n if (text === lastText) {\n return;\n }\n\n editor.update(\n () => {\n $clearGhostText();\n\n const selection = $getSelection();\n if (!text || !selection) {\n justRemovedGhostText = true;\n return;\n }\n\n const selectionCopy = selection.clone();\n\n const node = createNode(this.__id, text, this.__exposeText, this.__componentProps);\n ghostTextNodeKey = node.getKey();\n lastText = text;\n\n selection.insertNodes([node]);\n $setSelection(selectionCopy);\n },\n { tag: 'historic' },\n );\n };\n\n const handleUpdate: UpdateListener = props => {\n const { editorState, prevEditorState } = props;\n // If this update was caused by deleting the previous ghost text, don't create a new one until a subsequent update\n if (justRemovedGhostText) {\n justRemovedGhostText = false;\n return;\n }\n editorState.read(() => {\n const promise = this.__$getGhostText(editor, editorState, prevEditorState);\n promise.then(handleGhostTextResponse).catch(e => console.error(e));\n });\n };\n\n function unmountGhostText() {\n if (ghostTextNodeKey) {\n editor.update(\n () => {\n $clearGhostText();\n },\n { tag: 'historic' },\n );\n }\n }\n\n function $handleTabCommand(e: KeyboardEvent) {\n if (ghostTextNodeKey === null || lastText === null) {\n return false;\n }\n\n const ghostTextNode = $getNodeByKey(ghostTextNodeKey);\n if (!ghostTextNode) {\n return false;\n }\n\n e.preventDefault();\n\n const textNode = $createTextNode(lastText);\n ghostTextNode.replace(textNode);\n textNode.selectEnd();\n $clearGhostText();\n return true;\n }\n\n this.__cleanup = mergeRegister(\n editor.registerNodeTransform(nodeClass, handleGhostTextNodeTransform),\n editor.registerUpdateListener(handleUpdate),\n this.__allowCompletion ? editor.registerCommand(KEY_TAB_COMMAND, $handleTabCommand, COMMAND_PRIORITY_LOW) : noop,\n unmountGhostText,\n );\n }\n\n setExposeText(exposeText?: boolean) {\n this.__exposeText = exposeText;\n }\n\n setComponentProps(componentProps?: ComponentPropsType) {\n this.__componentProps = componentProps;\n }\n\n setGetGhostText($getGhostText: GetGhostTextFunction) {\n this.__$getGhostText = $getGhostText;\n }\n\n setAllowCompletion(allowCompletion?: boolean) {\n this.__allowCompletion = allowCompletion;\n }\n}\nfunction noop(): void {\n return;\n}\n"],"names":["$createTextNode","$getNodeByKey","$getSelection","$setSelection","COMMAND_PRIORITY_LOW","KEY_TAB_COMMAND","mergeRegister","GhostTextPluginBase","cleanup","__cleanup","setExposeText","exposeText","__exposeText","setComponentProps","componentProps","__componentProps","setGetGhostText","$getGhostText","__$getGhostText","setAllowCompletion","allowCompletion","__allowCompletion","constructor","editor","id","nodeClass","createNode","__id","ghostTextNodeKey","lastText","undefined","justRemovedGhostText","$clearGhostText","ghostTextNode","isAttached","remove","handleGhostTextNodeTransform","node","key","getKey","handleGhostTextResponse","text","update","selection","selectionCopy","clone","insertNodes","tag","handleUpdate","props","editorState","prevEditorState","read","promise","then","catch","e","console","error","unmountGhostText","$handleTabCommand","preventDefault","textNode","replace","selectEnd","registerNodeTransform","registerUpdateListener","registerCommand","noop"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["GhostText.base.ts"],"sourcesContent":["import type { EditorState, Klass, LexicalEditor, LexicalNode, UpdateListener } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getNodeByKey,\n $getSelection,\n $isRangeSelection,\n $isTextNode,\n $setSelection,\n COMMAND_PRIORITY_LOW,\n KEY_TAB_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { $isSentinelNode } from '../BasicFunctionality';\n\nexport type GetGhostTextFunction = (\n editor: LexicalEditor,\n editorState: EditorState,\n prevEditorState: EditorState,\n) => Promise<string | undefined>;\n\nexport interface IGhostTextNode<ComponentPropsType> extends LexicalNode {\n __content: string;\n __id: string;\n __allowCommitting?: boolean;\n __componentProps?: ComponentPropsType;\n __exposeText?: boolean;\n}\n\nexport class GhostTextPluginBase<ComponentPropsType> {\n private __id: string;\n private __$getGhostText: GetGhostTextFunction;\n private __componentProps?: ComponentPropsType;\n private __exposeText?: boolean;\n private __allowCompletion?: boolean;\n\n private __cleanup?: () => void;\n\n cleanup() {\n this.__cleanup?.();\n }\n\n constructor(\n editor: LexicalEditor,\n id: string,\n $getGhostText: GetGhostTextFunction,\n nodeClass: Klass<IGhostTextNode<ComponentPropsType>>,\n createNode: (\n id: string,\n content: string,\n exposeText?: boolean,\n componentProps?: ComponentPropsType,\n ) => IGhostTextNode<ComponentPropsType>,\n componentProps?: ComponentPropsType,\n // Whether or not the ghost text should count as text inside the input for submitting and character count\n exposeText?: boolean,\n allowCompletion?: boolean,\n ) {\n this.__id = id;\n this.__$getGhostText = $getGhostText;\n this.__componentProps = componentProps;\n this.__exposeText = exposeText;\n this.__allowCompletion = allowCompletion;\n\n let ghostTextNodeKey: string | null = null;\n let lastText: string | undefined = undefined;\n let justRemovedGhostText = false;\n let justAddedGhostText = false;\n\n function $clearGhostText() {\n const ghostTextNode = ghostTextNodeKey !== null ? $getNodeByKey(ghostTextNodeKey) : null;\n ghostTextNodeKey = null;\n lastText = undefined;\n if (ghostTextNode && ghostTextNode.isAttached()) {\n ghostTextNode.remove();\n justRemovedGhostText = true;\n }\n }\n function handleGhostTextNodeTransform(node: IGhostTextNode<ComponentPropsType>) {\n const key = node.getKey();\n\n if (node.__id === id && key !== ghostTextNodeKey) {\n // Only one ghost text\n node.remove();\n $clearGhostText();\n }\n }\n\n const handleGhostTextResponse = (text?: string) => {\n if (text === lastText) {\n return;\n }\n\n editor.update(\n () => {\n $clearGhostText();\n\n const selection = $getSelection();\n if (!text || !selection) {\n return;\n }\n\n const selectionCopy = selection.clone();\n\n const node = createNode(this.__id, text, this.__exposeText, this.__componentProps);\n ghostTextNodeKey = node.getKey();\n lastText = text;\n\n selection.insertNodes([node]);\n $setSelection(selectionCopy);\n justAddedGhostText = true;\n justRemovedGhostText = false;\n },\n { tag: 'historic' },\n );\n };\n\n const handleUpdate: UpdateListener = props => {\n const { editorState, prevEditorState } = props;\n\n // If this update was caused by adding or deleting ghost text, don't recheck the ghost text function until a subsequent update\n if (justRemovedGhostText || justAddedGhostText) {\n justRemovedGhostText = false;\n justAddedGhostText = false;\n return;\n }\n\n editorState.read(() => {\n // We only update the ghost text if the user selection is inside the input\n const selection = $getSelection();\n if (!$getSelection()) {\n return;\n }\n\n if ($isRangeSelection(selection) && selection.isCollapsed()) {\n // All the `$isXNode` functions prefer `null` over `undefined`\n const selectedNode = selection.getNodes().at(0) ?? null;\n const previousNodeKey = selectedNode?.getPreviousSibling()?.getKey();\n const previousNodeIsGhostText = previousNodeKey === ghostTextNodeKey;\n // If the ghost text is active and we're navigating past it, act as if the ghost text is not there and move 1 extra character\n if (\n previousNodeIsGhostText &&\n !$isSentinelNode(selectedNode) &&\n $isTextNode(selectedNode) &&\n selection.anchor.offset === 0\n ) {\n editor.update(\n () => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n selection.modify('move', false, 'character');\n }\n $clearGhostText();\n },\n { tag: 'historic' },\n );\n // Defer checking the ghost text until after this update has finished\n return;\n }\n\n // If the ghost text is the last node before the sentinel, we shouldn't let selection get past it\n if (previousNodeIsGhostText && $isSentinelNode(selectedNode)) {\n editor.update(\n () => {\n $getNodeByKey(previousNodeKey)?.getPreviousSibling()?.selectEnd();\n },\n { tag: 'historic' },\n );\n return;\n }\n }\n\n const promise = this.__$getGhostText(editor, editorState, prevEditorState);\n promise.then(handleGhostTextResponse).catch(e => console.error(e));\n });\n };\n\n function unmountGhostText() {\n if (ghostTextNodeKey) {\n editor.update(\n () => {\n $clearGhostText();\n },\n { tag: 'historic' },\n );\n }\n }\n\n function $handleTabCommand(e: KeyboardEvent) {\n if (ghostTextNodeKey === null || lastText === null) {\n return false;\n }\n\n const ghostTextNode = $getNodeByKey(ghostTextNodeKey);\n if (!ghostTextNode) {\n return false;\n }\n\n e.preventDefault();\n\n const textNode = $createTextNode(lastText);\n ghostTextNode.replace(textNode);\n textNode.selectEnd();\n $clearGhostText();\n return true;\n }\n\n this.__cleanup = mergeRegister(\n editor.registerNodeTransform(nodeClass, handleGhostTextNodeTransform),\n editor.registerUpdateListener(handleUpdate),\n this.__allowCompletion ? editor.registerCommand(KEY_TAB_COMMAND, $handleTabCommand, COMMAND_PRIORITY_LOW) : noop,\n unmountGhostText,\n );\n }\n\n setExposeText(exposeText?: boolean) {\n this.__exposeText = exposeText;\n }\n\n setComponentProps(componentProps?: ComponentPropsType) {\n this.__componentProps = componentProps;\n }\n\n setGetGhostText($getGhostText: GetGhostTextFunction) {\n this.__$getGhostText = $getGhostText;\n }\n\n setAllowCompletion(allowCompletion?: boolean) {\n this.__allowCompletion = allowCompletion;\n }\n}\nfunction noop(): void {\n return;\n}\n"],"names":["$createTextNode","$getNodeByKey","$getSelection","$isRangeSelection","$isTextNode","$setSelection","COMMAND_PRIORITY_LOW","KEY_TAB_COMMAND","mergeRegister","$isSentinelNode","GhostTextPluginBase","cleanup","__cleanup","setExposeText","exposeText","__exposeText","setComponentProps","componentProps","__componentProps","setGetGhostText","$getGhostText","__$getGhostText","setAllowCompletion","allowCompletion","__allowCompletion","constructor","editor","id","nodeClass","createNode","__id","ghostTextNodeKey","lastText","undefined","justRemovedGhostText","justAddedGhostText","$clearGhostText","ghostTextNode","isAttached","remove","handleGhostTextNodeTransform","node","key","getKey","handleGhostTextResponse","text","update","selection","selectionCopy","clone","insertNodes","tag","handleUpdate","props","editorState","prevEditorState","read","isCollapsed","selectedNode","getNodes","at","previousNodeKey","getPreviousSibling","previousNodeIsGhostText","anchor","offset","modify","selectEnd","promise","then","catch","e","console","error","unmountGhostText","$handleTabCommand","preventDefault","textNode","replace","registerNodeTransform","registerUpdateListener","registerCommand","noop"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _ as _define_property } from "@swc/helpers/_/_define_property";
|
|
2
2
|
import { $createParagraphNode, $createRangeSelection, $createTextNode, $getLeafNodes, $getRoot, $getSelection, $isTextNode, $normalizeSelection__EXPERIMENTAL, $setSelection } from '@fluentui-copilot/text-editor';
|
|
3
|
+
import { SENTINEL_VALUE } from '../BasicFunctionality';
|
|
3
4
|
export class ImperativeControlBase {
|
|
4
5
|
moveCursor(location) {
|
|
5
6
|
this.__editor.update(() => {
|
|
@@ -52,12 +53,13 @@ export class ImperativeControlBase {
|
|
|
52
53
|
insertTextAtCursor(text) {
|
|
53
54
|
this.__editor.update(() => {
|
|
54
55
|
var _$getSelection;
|
|
55
|
-
(_$getSelection = $getSelection())
|
|
56
|
+
const selection = (_$getSelection = $getSelection()) !== null && _$getSelection !== void 0 ? _$getSelection : $getRoot().selectEnd();
|
|
57
|
+
selection.insertText(text);
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
60
|
getInputText() {
|
|
59
61
|
return this.__editor.getEditorState().read(() => {
|
|
60
|
-
return $getRoot().getTextContent();
|
|
62
|
+
return $getRoot().getTextContent().replace(SENTINEL_VALUE, '');
|
|
61
63
|
});
|
|
62
64
|
}
|
|
63
65
|
scrollToBottom() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["ImperativeControl.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createParagraphNode,\n $createRangeSelection,\n $createTextNode,\n $getLeafNodes,\n $getRoot,\n $getSelection,\n $isTextNode,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n} from '@fluentui-copilot/text-editor';\n\nexport interface IImperativeControlBase {\n setInputText: (inputText: string) => void;\n appendText: (text: string) => void;\n prependText: (text: string) => void;\n insertTextAtCursor: (text: string) => void;\n getInputText: () => string;\n scrollToBottom: () => void;\n moveCursor: (location: number) => void;\n}\n\nexport class ImperativeControlBase implements IImperativeControlBase {\n private __editor: LexicalEditor;\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n }\n\n moveCursor(location: number): void {\n this.__editor.update(() => {\n const children = $getLeafNodes($getRoot());\n\n let baseOffset = 0;\n let currentNode = children.shift();\n while (baseOffset < location && currentNode) {\n const nodeLength =\n $isTextNode(currentNode) && !currentNode.isToken()\n ? currentNode.getTextContent().length\n : // Token text nodes and non-text nodes are considered to be a single entry in the input\n 1;\n\n if (baseOffset + nodeLength >= location) {\n const elementType = $isTextNode(currentNode) ? 'text' : 'element';\n const localOffset = location - baseOffset;\n const nodeKey = currentNode.getKey();\n\n const selection = $createRangeSelection();\n selection.anchor.set(nodeKey, localOffset, elementType);\n selection.focus.set(nodeKey, localOffset, elementType);\n\n $setSelection($normalizeSelection__EXPERIMENTAL(selection));\n return;\n }\n\n baseOffset += nodeLength;\n currentNode = children.shift();\n }\n\n if (location > baseOffset) {\n $getRoot().selectEnd();\n }\n });\n }\n setInputText(inputText: string) {\n this.__editor.update(() => {\n const root = $getRoot();\n root.clear();\n if (inputText !== '') {\n const newParagraph = $createParagraphNode();\n const newText = $createTextNode(inputText);\n\n newParagraph.append(newText);\n root.append(newParagraph);\n root.selectEnd();\n }\n });\n }\n appendText(text: string) {\n this.__editor.update(() => {\n $getRoot().selectEnd().insertText(text);\n });\n }\n prependText(text: string) {\n this.__editor.update(() => {\n $getRoot().selectStart().insertText(text);\n });\n }\n insertTextAtCursor(text: string) {\n this.__editor.update(() => {\n $getSelection()
|
|
1
|
+
{"version":3,"sources":["ImperativeControl.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createParagraphNode,\n $createRangeSelection,\n $createTextNode,\n $getLeafNodes,\n $getRoot,\n $getSelection,\n $isTextNode,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n} from '@fluentui-copilot/text-editor';\nimport { SENTINEL_VALUE } from '../BasicFunctionality';\n\nexport interface IImperativeControlBase {\n setInputText: (inputText: string) => void;\n appendText: (text: string) => void;\n prependText: (text: string) => void;\n insertTextAtCursor: (text: string) => void;\n getInputText: () => string;\n scrollToBottom: () => void;\n moveCursor: (location: number) => void;\n}\n\nexport class ImperativeControlBase implements IImperativeControlBase {\n private __editor: LexicalEditor;\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n }\n\n moveCursor(location: number): void {\n this.__editor.update(() => {\n const children = $getLeafNodes($getRoot());\n\n let baseOffset = 0;\n let currentNode = children.shift();\n while (baseOffset < location && currentNode) {\n const nodeLength =\n $isTextNode(currentNode) && !currentNode.isToken()\n ? currentNode.getTextContent().length\n : // Token text nodes and non-text nodes are considered to be a single entry in the input\n 1;\n\n if (baseOffset + nodeLength >= location) {\n const elementType = $isTextNode(currentNode) ? 'text' : 'element';\n const localOffset = location - baseOffset;\n const nodeKey = currentNode.getKey();\n\n const selection = $createRangeSelection();\n selection.anchor.set(nodeKey, localOffset, elementType);\n selection.focus.set(nodeKey, localOffset, elementType);\n\n $setSelection($normalizeSelection__EXPERIMENTAL(selection));\n return;\n }\n\n baseOffset += nodeLength;\n currentNode = children.shift();\n }\n\n if (location > baseOffset) {\n $getRoot().selectEnd();\n }\n });\n }\n setInputText(inputText: string) {\n this.__editor.update(() => {\n const root = $getRoot();\n root.clear();\n if (inputText !== '') {\n const newParagraph = $createParagraphNode();\n const newText = $createTextNode(inputText);\n\n newParagraph.append(newText);\n root.append(newParagraph);\n root.selectEnd();\n }\n });\n }\n appendText(text: string) {\n this.__editor.update(() => {\n $getRoot().selectEnd().insertText(text);\n });\n }\n prependText(text: string) {\n this.__editor.update(() => {\n $getRoot().selectStart().insertText(text);\n });\n }\n insertTextAtCursor(text: string) {\n this.__editor.update(() => {\n const selection = $getSelection() ?? $getRoot().selectEnd();\n selection.insertText(text);\n });\n }\n getInputText() {\n return this.__editor.getEditorState().read(() => {\n return $getRoot().getTextContent().replace(SENTINEL_VALUE, '');\n });\n }\n scrollToBottom() {\n this.__editor.getRootElement()?.scrollIntoView({ behavior: 'smooth', block: 'end' });\n return;\n }\n}\n"],"names":["$createParagraphNode","$createRangeSelection","$createTextNode","$getLeafNodes","$getRoot","$getSelection","$isTextNode","$normalizeSelection__EXPERIMENTAL","$setSelection","SENTINEL_VALUE","ImperativeControlBase","moveCursor","location","__editor","update","children","baseOffset","currentNode","shift","nodeLength","isToken","getTextContent","length","elementType","localOffset","nodeKey","getKey","selection","anchor","set","focus","selectEnd","setInputText","inputText","root","clear","newParagraph","newText","append","appendText","text","insertText","prependText","selectStart","insertTextAtCursor","getInputText","getEditorState","read","replace","scrollToBottom","getRootElement","scrollIntoView","behavior","block","constructor","editor"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";AACA,SACEA,oBAAoB,EACpBC,qBAAqB,EACrBC,eAAe,EACfC,aAAa,EACbC,QAAQ,EACRC,aAAa,EACbC,WAAW,EACXC,iCAAiC,EACjCC,aAAa,QACR,gCAAgC;AACvC,SAASC,cAAc,QAAQ,wBAAwB;AAYvD,OAAO,MAAMC;IAOXC,WAAWC,QAAgB,EAAQ;QACjC,IAAI,CAACC,QAAQ,CAACC,MAAM,CAAC;YACnB,MAAMC,WAAWZ,cAAcC;YAE/B,IAAIY,aAAa;YACjB,IAAIC,cAAcF,SAASG,KAAK;YAChC,MAAOF,aAAaJ,YAAYK,YAAa;gBAC3C,MAAME,aACJb,YAAYW,gBAAgB,CAACA,YAAYG,OAAO,KAC5CH,YAAYI,cAAc,GAAGC,MAAM,GAEnC;gBAEN,IAAIN,aAAaG,cAAcP,UAAU;oBACvC,MAAMW,cAAcjB,YAAYW,eAAe,SAAS;oBACxD,MAAMO,cAAcZ,WAAWI;oBAC/B,MAAMS,UAAUR,YAAYS,MAAM;oBAElC,MAAMC,YAAY1B;oBAClB0B,UAAUC,MAAM,CAACC,GAAG,CAACJ,SAASD,aAAaD;oBAC3CI,UAAUG,KAAK,CAACD,GAAG,CAACJ,SAASD,aAAaD;oBAE1Cf,cAAcD,kCAAkCoB;oBAChD;gBACF;gBAEAX,cAAcG;gBACdF,cAAcF,SAASG,KAAK;YAC9B;YAEA,IAAIN,WAAWI,YAAY;gBACzBZ,WAAW2B,SAAS;YACtB;QACF;IACF;IACAC,aAAaC,SAAiB,EAAE;QAC9B,IAAI,CAACpB,QAAQ,CAACC,MAAM,CAAC;YACnB,MAAMoB,OAAO9B;YACb8B,KAAKC,KAAK;YACV,IAAIF,cAAc,IAAI;gBACpB,MAAMG,eAAepC;gBACrB,MAAMqC,UAAUnC,gBAAgB+B;gBAEhCG,aAAaE,MAAM,CAACD;gBACpBH,KAAKI,MAAM,CAACF;gBACZF,KAAKH,SAAS;YAChB;QACF;IACF;IACAQ,WAAWC,IAAY,EAAE;QACvB,IAAI,CAAC3B,QAAQ,CAACC,MAAM,CAAC;YACnBV,WAAW2B,SAAS,GAAGU,UAAU,CAACD;QACpC;IACF;IACAE,YAAYF,IAAY,EAAE;QACxB,IAAI,CAAC3B,QAAQ,CAACC,MAAM,CAAC;YACnBV,WAAWuC,WAAW,GAAGF,UAAU,CAACD;QACtC;IACF;IACAI,mBAAmBJ,IAAY,EAAE;QAC/B,IAAI,CAAC3B,QAAQ,CAACC,MAAM,CAAC;gBACDT;YAAlB,MAAMsB,YAAYtB,CAAAA,iBAAAA,6BAAAA,4BAAAA,iBAAmBD,WAAW2B,SAAS;YACzDJ,UAAUc,UAAU,CAACD;QACvB;IACF;IACAK,eAAe;QACb,OAAO,IAAI,CAAChC,QAAQ,CAACiC,cAAc,GAAGC,IAAI,CAAC;YACzC,OAAO3C,WAAWiB,cAAc,GAAG2B,OAAO,CAACvC,gBAAgB;QAC7D;IACF;IACAwC,iBAAiB;YACf;SAAA,gCAAA,IAAI,CAACpC,QAAQ,CAACqC,cAAc,gBAA5B,oDAAA,8BAAgCC,cAAc,CAAC;YAAEC,UAAU;YAAUC,OAAO;QAAM;QAClF;IACF;IA7EAC,YAAYC,MAAqB,CAAE;QAFnC,uBAAQ1C,YAAR,KAAA;QAGE,IAAI,CAACA,QAAQ,GAAG0C;IAClB;AA4EF"}
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BasicFunctionalityBase, SentinelNode, $createSentinelNode, $isSentinelNode } from './BasicFunctionality';
|
|
1
|
+
export { BasicFunctionalityBase, SentinelNode, $createSentinelNode, $isSentinelNode, SENTINEL_VALUE } from './BasicFunctionality';
|
|
2
2
|
export { ChatInputEntityPluginBase } from './ChatInputEntity';
|
|
3
3
|
export { GhostTextPluginBase } from './GhostText';
|
|
4
4
|
export { ImperativeControlBase } from './ImperativeControl';
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["index.ts"],"sourcesContent":["export {
|
|
1
|
+
{"version":3,"sources":["index.ts"],"sourcesContent":["export {\n BasicFunctionalityBase,\n SentinelNode,\n $createSentinelNode,\n $isSentinelNode,\n SENTINEL_VALUE,\n} from './BasicFunctionality';\nexport type { IBasicFunctionalityBase } from './BasicFunctionality';\n\nexport { ChatInputEntityPluginBase } from './ChatInputEntity';\nexport type {\n ChatInputEntityData,\n ChatInputEntityPluginProps,\n IChatInputEntityPluginBase,\n IEntityNode,\n} from './ChatInputEntity';\n\nexport { GhostTextPluginBase } from './GhostText';\nexport type { GetGhostTextFunction, IGhostTextNode } from './GhostText';\n\nexport { ImperativeControlBase } from './ImperativeControl';\nexport type { IImperativeControlBase } from './ImperativeControl';\n\nexport { ManualGhostTextBase } from './ManualGhostText';\nexport type { IManualGhostTextBase } from './ManualGhostText';\n"],"names":["BasicFunctionalityBase","SentinelNode","$createSentinelNode","$isSentinelNode","SENTINEL_VALUE","ChatInputEntityPluginBase","GhostTextPluginBase","ImperativeControlBase","ManualGhostTextBase"],"rangeMappings":";;;;","mappings":"AAAA,SACEA,sBAAsB,EACtBC,YAAY,EACZC,mBAAmB,EACnBC,eAAe,EACfC,cAAc,QACT,uBAAuB;AAG9B,SAASC,yBAAyB,QAAQ,oBAAoB;AAQ9D,SAASC,mBAAmB,QAAQ,cAAc;AAGlD,SAASC,qBAAqB,QAAQ,sBAAsB;AAG5D,SAASC,mBAAmB,QAAQ,oBAAoB"}
|
|
@@ -40,7 +40,7 @@ class BasicFunctionalityBase {
|
|
|
40
40
|
this.deactivateContentCallbacks();
|
|
41
41
|
this.__contentChangeCleanup = this.__editor.registerTextContentListener((text)=>{
|
|
42
42
|
// Remove the sentinel node
|
|
43
|
-
const processed = text.replace(
|
|
43
|
+
const processed = text.replace(_SentinelNode.SENTINEL_VALUE, '');
|
|
44
44
|
onContentChange === null || onContentChange === void 0 ? void 0 : onContentChange(processed);
|
|
45
45
|
onCountChanged === null || onCountChanged === void 0 ? void 0 : onCountChanged(processed.length);
|
|
46
46
|
});
|
|
@@ -115,19 +115,22 @@ class BasicFunctionalityBase {
|
|
|
115
115
|
if (leaves.length === 0) {
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
|
-
const
|
|
118
|
+
const lastNode = leaves[leaves.length - 1];
|
|
119
|
+
const lastNodeKey = lastNode.getKey();
|
|
119
120
|
// If the last node isn't a sentinel, add one
|
|
120
|
-
if (!(0, _SentinelNode.$isSentinelNode)(
|
|
121
|
+
if (!(0, _SentinelNode.$isSentinelNode)(lastNode)) {
|
|
121
122
|
this.__editor.update(()=>{
|
|
122
|
-
|
|
123
|
+
var // We find the node by its key again in case the node was removed before this update runs
|
|
124
|
+
_$getNodeByKey;
|
|
125
|
+
(_$getNodeByKey = (0, _texteditor.$getNodeByKey)(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.insertAfter((0, _SentinelNode.$createSentinelNode)());
|
|
123
126
|
}, {
|
|
124
127
|
discrete: true
|
|
125
128
|
});
|
|
126
129
|
return;
|
|
127
130
|
}
|
|
128
131
|
// If the sentinel node is not selected, we're done
|
|
129
|
-
const previous =
|
|
130
|
-
if (!previous || !
|
|
132
|
+
const previous = lastNode.getPreviousSibling();
|
|
133
|
+
if (!previous || !lastNode.isSelected()) {
|
|
131
134
|
return;
|
|
132
135
|
}
|
|
133
136
|
const selection = (0, _texteditor.$getSelection)();
|
|
@@ -139,7 +142,8 @@ class BasicFunctionalityBase {
|
|
|
139
142
|
// where selection is ill-defined.
|
|
140
143
|
if (selection.isCollapsed() && selection.anchor.offset > 0) {
|
|
141
144
|
this.__editor.update(()=>{
|
|
142
|
-
|
|
145
|
+
var _$getNodeByKey;
|
|
146
|
+
(_$getNodeByKey = (0, _texteditor.$getNodeByKey)(lastNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : _$getNodeByKey.selectStart();
|
|
143
147
|
}, {
|
|
144
148
|
discrete: true
|
|
145
149
|
});
|
|
@@ -149,17 +153,19 @@ class BasicFunctionalityBase {
|
|
|
149
153
|
if (!selection.isCollapsed()) {
|
|
150
154
|
let selectionChanged = false;
|
|
151
155
|
const newSelection = selection.clone();
|
|
152
|
-
if (newSelection.anchor.getNode() ===
|
|
153
|
-
newSelection.anchor.set(
|
|
156
|
+
if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {
|
|
157
|
+
newSelection.anchor.set(lastNodeKey, 0, 'text');
|
|
154
158
|
selectionChanged = true;
|
|
155
159
|
}
|
|
156
|
-
if (newSelection.focus.getNode() ===
|
|
157
|
-
newSelection.focus.set(
|
|
160
|
+
if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {
|
|
161
|
+
newSelection.focus.set(lastNodeKey, 0, 'text');
|
|
158
162
|
selectionChanged = true;
|
|
159
163
|
}
|
|
160
164
|
if (selectionChanged) {
|
|
161
165
|
this.__editor.update(()=>{
|
|
162
|
-
|
|
166
|
+
if ((0, _texteditor.$getNodeByKey)(lastNodeKey) !== null) {
|
|
167
|
+
(0, _texteditor.$setSelection)((0, _texteditor.$normalizeSelection__EXPERIMENTAL)(newSelection));
|
|
168
|
+
}
|
|
163
169
|
}, {
|
|
164
170
|
discrete: true
|
|
165
171
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["BasicFunctionality.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getLeafNodes,\n $getRoot,\n $getSelection,\n $insertNodes,\n $isRangeSelection,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n COMMAND_PRIORITY_CRITICAL,\n INSERT_PARAGRAPH_COMMAND,\n KEY_ENTER_COMMAND,\n PASTE_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { $createSentinelNode, $isSentinelNode, SentinelNode } from './SentinelNode';\n\nexport interface IBasicFunctionalityBase {\n insertDefaultValue: (defaultValue: string) => void;\n setIsDisabled: (isDisabled: boolean) => void;\n activateContentCallbacks(onContentChange?: (value: string) => void, onCountChanged?: (count: number) => void): void;\n deactivateContentCallbacks(): void;\n activateTrimWhitespace(): void;\n deactivateTrimWhitespace(): void;\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void): void;\n deactivatePasteCallback(): void;\n cleanup(): void;\n}\n\nexport class BasicFunctionalityBase implements IBasicFunctionalityBase {\n private __editor: LexicalEditor;\n private __contentChangeCleanup?: () => void;\n private __trimWhitespaceCleanup?: () => void;\n private __baseHandlersCleanup?: () => void;\n private __pasteHandlerCleanup?: () => void;\n\n private __enterHandler(event: KeyboardEvent) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return false;\n }\n\n if (event === null) {\n return false;\n }\n\n event.preventDefault();\n\n if (event.shiftKey) {\n return this.__editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);\n }\n\n // Mark event handled to override default behavior\n return true;\n }\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n\n this.__baseHandlersCleanup = mergeRegister(\n this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_CRITICAL),\n\n // Add a sentinel node at the end of the input when there is content.\n // This sentinel node fixes a number of issues.\n // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends\n // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487\n // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.\n // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input\n // mitigates these issues.\n this.__editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const leaves = $getLeafNodes($getRoot());\n if (leaves.length === 0) {\n return;\n }\n\n const sentinel = leaves[leaves.length - 1];\n\n // If the last node isn't a sentinel, add one\n if (!$isSentinelNode(sentinel)) {\n this.__editor.update(\n () => {\n sentinel.insertAfter($createSentinelNode());\n },\n { discrete: true },\n );\n return;\n }\n\n // If the sentinel node is not selected, we're done\n const previous = sentinel.getPreviousSibling();\n if (!previous || !sentinel.isSelected()) {\n return;\n }\n\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return;\n }\n\n // If the cursor is inside the sentinel node, move it out (next to the beginning)\n // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node\n // where selection is ill-defined.\n if (selection.isCollapsed() && selection.anchor.offset > 0) {\n this.__editor.update(\n () => {\n sentinel.selectStart();\n },\n { discrete: true },\n );\n return;\n }\n\n // If the selection is a range which includes the sentinel, modify the range to exclude it\n if (!selection.isCollapsed()) {\n let selectionChanged = false;\n const newSelection = selection.clone();\n\n if (newSelection.anchor.getNode() === sentinel && newSelection.anchor.offset > 0) {\n newSelection.anchor.set(sentinel.getKey(), 0, 'text');\n selectionChanged = true;\n }\n if (newSelection.focus.getNode() === sentinel && newSelection.focus.offset > 0) {\n newSelection.focus.set(sentinel.getKey(), 0, 'text');\n selectionChanged = true;\n }\n\n if (selectionChanged) {\n this.__editor.update(\n () => {\n $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));\n },\n { discrete: true },\n );\n }\n }\n });\n }),\n this.__editor.registerNodeTransform(SentinelNode, node => {\n if (!node.getPreviousSibling() || node.getNextSibling()) {\n node.remove();\n return;\n }\n }),\n );\n }\n\n insertDefaultValue(defaultValue: string) {\n if (defaultValue) {\n this.__editor.update(() => {\n $insertNodes([$createTextNode(defaultValue)]);\n });\n }\n }\n\n activateContentCallbacks(\n onContentChange?: ((value: string) => void) | undefined,\n onCountChanged?: ((count: number) => void) | undefined,\n ) {\n this.deactivateContentCallbacks();\n this.__contentChangeCleanup = this.__editor.registerTextContentListener(text => {\n // Remove the sentinel node\n const processed = text.replace('\\u200b\\u200c', '');\n onContentChange?.(processed);\n onCountChanged?.(processed.length);\n });\n }\n\n deactivateContentCallbacks() {\n this.__contentChangeCleanup?.();\n this.__contentChangeCleanup = undefined;\n }\n\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void) {\n this.__pasteHandlerCleanup = this.__editor.registerCommand(\n PASTE_COMMAND,\n (event: ClipboardEvent) => {\n onPaste(event);\n\n if (event.defaultPrevented) {\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n }\n\n deactivatePasteCallback() {\n this.__pasteHandlerCleanup?.();\n this.__pasteHandlerCleanup = undefined;\n }\n\n activateTrimWhitespace() {\n this.deactivateTrimWhitespace();\n this.__trimWhitespaceCleanup = this.__editor.registerTextContentListener(text => {\n if (text.trim() === '') {\n this.__editor.update(\n () => {\n $getRoot()\n .getAllTextNodes()\n .forEach(node => {\n node.remove();\n });\n },\n // Don't allow undoing this action\n { tag: 'historic' },\n );\n }\n });\n }\n\n deactivateTrimWhitespace() {\n this.__trimWhitespaceCleanup?.();\n this.__trimWhitespaceCleanup = undefined;\n }\n\n setIsDisabled(isDisabled: boolean) {\n this.__editor.setEditable(!isDisabled);\n }\n\n cleanup() {\n this.deactivateContentCallbacks();\n this.deactivateTrimWhitespace();\n this.deactivatePasteCallback();\n this.__baseHandlersCleanup?.();\n this.__baseHandlersCleanup = undefined;\n }\n}\n"],"names":["BasicFunctionalityBase","__enterHandler","event","selection","$getSelection","$isRangeSelection","preventDefault","shiftKey","__editor","dispatchCommand","INSERT_PARAGRAPH_COMMAND","insertDefaultValue","defaultValue","update","$insertNodes","onContentChange","onCountChanged","deactivateContentCallbacks","activateContentCallbacks","text","processed","__contentChangeCleanup","registerTextContentListener","length","_this___contentChangeCleanup","_this","call","undefined","__pasteHandlerCleanup","registerCommand","PASTE_COMMAND","activatePasteCallback","onPaste","COMMAND_PRIORITY_CRITICAL","_this___pasteHandlerCleanup","deactivatePasteCallback","deactivateTrimWhitespace","activateTrimWhitespace","__trimWhitespaceCleanup","trim","forEach","node","remove","_this___trimWhitespaceCleanup","isDisabled","setIsDisabled","cleanup","__baseHandlersCleanup","_this___baseHandlersCleanup","editor","constructor","registerUpdateListener","editorState","read","leaves","$getLeafNodes","$getRoot","$isSentinelNode","sentinel","insertAfter","discrete","getPreviousSibling","isSelected","selectStart","anchor","offset","isCollapsed","newSelection","set","selectionChanged","getKey","focus","$setSelection","SentinelNode","getNextSibling"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["BasicFunctionality.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getLeafNodes,\n $getNodeByKey,\n $getRoot,\n $getSelection,\n $insertNodes,\n $isRangeSelection,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n COMMAND_PRIORITY_CRITICAL,\n INSERT_PARAGRAPH_COMMAND,\n KEY_ENTER_COMMAND,\n PASTE_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { $createSentinelNode, $isSentinelNode, SENTINEL_VALUE, SentinelNode } from './SentinelNode';\n\nexport interface IBasicFunctionalityBase {\n insertDefaultValue: (defaultValue: string) => void;\n setIsDisabled: (isDisabled: boolean) => void;\n activateContentCallbacks(onContentChange?: (value: string) => void, onCountChanged?: (count: number) => void): void;\n deactivateContentCallbacks(): void;\n activateTrimWhitespace(): void;\n deactivateTrimWhitespace(): void;\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void): void;\n deactivatePasteCallback(): void;\n cleanup(): void;\n}\n\nexport class BasicFunctionalityBase implements IBasicFunctionalityBase {\n private __editor: LexicalEditor;\n private __contentChangeCleanup?: () => void;\n private __trimWhitespaceCleanup?: () => void;\n private __baseHandlersCleanup?: () => void;\n private __pasteHandlerCleanup?: () => void;\n\n private __enterHandler(event: KeyboardEvent) {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return false;\n }\n\n if (event === null) {\n return false;\n }\n\n event.preventDefault();\n\n if (event.shiftKey) {\n return this.__editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);\n }\n\n // Mark event handled to override default behavior\n return true;\n }\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n\n this.__baseHandlersCleanup = mergeRegister(\n this.__editor.registerCommand(KEY_ENTER_COMMAND, this.__enterHandler.bind(this), COMMAND_PRIORITY_CRITICAL),\n\n // Add a sentinel node at the end of the input when there is content.\n // This sentinel node fixes a number of issues.\n // In Safari, Lexical's behaviour of adding <br /> tags to the end of the input when it ends\n // in a decorator node causes cursor location issues: https://github.com/facebook/lexical/issues/4487\n // Otherwise, when a decorator node is the last node in the input, the cursor can't move past it.\n // Adding an invisible text node that doesn't contribute to the content and can't be selected to the end of the input\n // mitigates these issues.\n this.__editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const leaves = $getLeafNodes($getRoot());\n if (leaves.length === 0) {\n return;\n }\n\n const lastNode = leaves[leaves.length - 1];\n const lastNodeKey = lastNode.getKey();\n\n // If the last node isn't a sentinel, add one\n if (!$isSentinelNode(lastNode)) {\n this.__editor.update(\n () => {\n // We find the node by its key again in case the node was removed before this update runs\n $getNodeByKey(lastNodeKey)?.insertAfter($createSentinelNode());\n },\n { discrete: true },\n );\n return;\n }\n\n // If the sentinel node is not selected, we're done\n const previous = lastNode.getPreviousSibling();\n if (!previous || !lastNode.isSelected()) {\n return;\n }\n\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n return;\n }\n\n // If the cursor is inside the sentinel node, move it out (next to the beginning)\n // We allow selection on the boundary of the sentinel in case the adjacent node is a decorator node\n // where selection is ill-defined.\n if (selection.isCollapsed() && selection.anchor.offset > 0) {\n this.__editor.update(\n () => {\n $getNodeByKey(lastNodeKey)?.selectStart();\n },\n { discrete: true },\n );\n return;\n }\n\n // If the selection is a range which includes the sentinel, modify the range to exclude it\n if (!selection.isCollapsed()) {\n let selectionChanged = false;\n const newSelection = selection.clone();\n\n if (newSelection.anchor.getNode() === lastNode && newSelection.anchor.offset > 0) {\n newSelection.anchor.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n if (newSelection.focus.getNode() === lastNode && newSelection.focus.offset > 0) {\n newSelection.focus.set(lastNodeKey, 0, 'text');\n selectionChanged = true;\n }\n\n if (selectionChanged) {\n this.__editor.update(\n () => {\n if ($getNodeByKey(lastNodeKey) !== null) {\n $setSelection($normalizeSelection__EXPERIMENTAL(newSelection));\n }\n },\n { discrete: true },\n );\n }\n }\n });\n }),\n this.__editor.registerNodeTransform(SentinelNode, node => {\n if (!node.getPreviousSibling() || node.getNextSibling()) {\n node.remove();\n return;\n }\n }),\n );\n }\n\n insertDefaultValue(defaultValue: string) {\n if (defaultValue) {\n this.__editor.update(() => {\n $insertNodes([$createTextNode(defaultValue)]);\n });\n }\n }\n\n activateContentCallbacks(\n onContentChange?: ((value: string) => void) | undefined,\n onCountChanged?: ((count: number) => void) | undefined,\n ) {\n this.deactivateContentCallbacks();\n this.__contentChangeCleanup = this.__editor.registerTextContentListener(text => {\n // Remove the sentinel node\n const processed = text.replace(SENTINEL_VALUE, '');\n onContentChange?.(processed);\n onCountChanged?.(processed.length);\n });\n }\n\n deactivateContentCallbacks() {\n this.__contentChangeCleanup?.();\n this.__contentChangeCleanup = undefined;\n }\n\n activatePasteCallback(onPaste: (event: ClipboardEvent) => void) {\n this.__pasteHandlerCleanup = this.__editor.registerCommand(\n PASTE_COMMAND,\n (event: ClipboardEvent) => {\n onPaste(event);\n\n if (event.defaultPrevented) {\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n }\n\n deactivatePasteCallback() {\n this.__pasteHandlerCleanup?.();\n this.__pasteHandlerCleanup = undefined;\n }\n\n activateTrimWhitespace() {\n this.deactivateTrimWhitespace();\n this.__trimWhitespaceCleanup = this.__editor.registerTextContentListener(text => {\n if (text.trim() === '') {\n this.__editor.update(\n () => {\n $getRoot()\n .getAllTextNodes()\n .forEach(node => {\n node.remove();\n });\n },\n // Don't allow undoing this action\n { tag: 'historic' },\n );\n }\n });\n }\n\n deactivateTrimWhitespace() {\n this.__trimWhitespaceCleanup?.();\n this.__trimWhitespaceCleanup = undefined;\n }\n\n setIsDisabled(isDisabled: boolean) {\n this.__editor.setEditable(!isDisabled);\n }\n\n cleanup() {\n this.deactivateContentCallbacks();\n this.deactivateTrimWhitespace();\n this.deactivatePasteCallback();\n this.__baseHandlersCleanup?.();\n this.__baseHandlersCleanup = undefined;\n }\n}\n"],"names":["BasicFunctionalityBase","__enterHandler","event","selection","$getSelection","$isRangeSelection","preventDefault","shiftKey","__editor","dispatchCommand","INSERT_PARAGRAPH_COMMAND","insertDefaultValue","defaultValue","update","$insertNodes","onContentChange","onCountChanged","deactivateContentCallbacks","activateContentCallbacks","text","processed","__contentChangeCleanup","SENTINEL_VALUE","registerTextContentListener","length","_this___contentChangeCleanup","_this","call","undefined","__pasteHandlerCleanup","registerCommand","PASTE_COMMAND","activatePasteCallback","onPaste","COMMAND_PRIORITY_CRITICAL","_this___pasteHandlerCleanup","deactivatePasteCallback","deactivateTrimWhitespace","activateTrimWhitespace","__trimWhitespaceCleanup","trim","forEach","node","remove","_this___trimWhitespaceCleanup","isDisabled","setIsDisabled","cleanup","__baseHandlersCleanup","_this___baseHandlersCleanup","editor","constructor","registerUpdateListener","editorState","read","leaves","$getLeafNodes","$getRoot","$isSentinelNode","lastNode","$getNodeByKey","lastNodeKey","_$getNodeByKey","insertAfter","$createSentinelNode","getPreviousSibling","selectStart","isCollapsed","newSelection","anchor","selectionChanged","getNode","offset","focus","$setSelection","discrete","registerNodeTransform","SentinelNode","getNextSibling"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
@@ -15,11 +15,15 @@ _export(exports, {
|
|
|
15
15
|
$isSentinelNode: function() {
|
|
16
16
|
return $isSentinelNode;
|
|
17
17
|
},
|
|
18
|
+
SENTINEL_VALUE: function() {
|
|
19
|
+
return SENTINEL_VALUE;
|
|
20
|
+
},
|
|
18
21
|
SentinelNode: function() {
|
|
19
22
|
return SentinelNode;
|
|
20
23
|
}
|
|
21
24
|
});
|
|
22
25
|
const _texteditor = require("@fluentui-copilot/text-editor");
|
|
26
|
+
const SENTINEL_VALUE = '\u200b\u200c';
|
|
23
27
|
class SentinelNode extends _texteditor.TextNode {
|
|
24
28
|
static getType() {
|
|
25
29
|
return 'sentinel';
|
|
@@ -33,7 +37,7 @@ class SentinelNode extends _texteditor.TextNode {
|
|
|
33
37
|
constructor(key){
|
|
34
38
|
// 2 zero width characters will not be visible in the editor.
|
|
35
39
|
// These also happen to be markers for CIQ to ignore the following content.
|
|
36
|
-
super(
|
|
40
|
+
super(SENTINEL_VALUE, key);
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
function $createSentinelNode(key) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["SentinelNode.ts"],"sourcesContent":["import type { LexicalNode, NodeKey } from '@fluentui-copilot/text-editor';\nimport { TextNode } from '@fluentui-copilot/text-editor';\n\nexport class SentinelNode extends TextNode {\n constructor(key?: NodeKey) {\n // 2 zero width characters will not be visible in the editor.\n // These also happen to be markers for CIQ to ignore the following content.\n super(
|
|
1
|
+
{"version":3,"sources":["SentinelNode.ts"],"sourcesContent":["import type { LexicalNode, NodeKey } from '@fluentui-copilot/text-editor';\nimport { TextNode } from '@fluentui-copilot/text-editor';\n\nexport const SENTINEL_VALUE = '\\u200b\\u200c';\n\nexport class SentinelNode extends TextNode {\n constructor(key?: NodeKey) {\n // 2 zero width characters will not be visible in the editor.\n // These also happen to be markers for CIQ to ignore the following content.\n super(SENTINEL_VALUE, key);\n }\n\n static getType() {\n return 'sentinel';\n }\n static clone(node: SentinelNode) {\n return new SentinelNode(node.__key);\n }\n\n isToken() {\n return true;\n }\n}\n\nexport function $createSentinelNode(key?: NodeKey) {\n return new SentinelNode(key);\n}\n\nexport function $isSentinelNode(node: LexicalNode | null): node is SentinelNode {\n return node instanceof SentinelNode;\n}\n"],"names":["$createSentinelNode","$isSentinelNode","SENTINEL_VALUE","SentinelNode","TextNode","getType","clone","node","__key","isToken","constructor","key"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;;;;;;;;IAwBgBA,mBAAAA;eAAAA;;IAIAC,eAAAA;eAAAA;;IAzBHC,cAAAA;eAAAA;;IAEAC,YAAAA;eAAAA;;;4BAJY;AAElB,MAAMD,iBAAiB;AAEvB,MAAMC,qBAAqBC,oBAAAA;WAOhCC,UAAOA;eACL;;WAEFC,MAAOA,IAAMC,EAAkB;eAC7B,IAAOJ,aAAIA,KAAaI,KAAKC;;cAG/BC;eACE;;gBAdFC,GAAYC,CAAa;qEACvB;mFACA;aACA,CAAAT,gBAAMA;;AAaV;AAEO,SAASF,oBAAoBW,GAAa;WAC/C,IAAOR,aAAIA;AACb;AAEO,SAASF,gBAAgBM,IAAwB;WACtDA,gBAAOA;AACT"}
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "GhostTextPluginBase", {
|
|
|
10
10
|
});
|
|
11
11
|
const _define_property = require("@swc/helpers/_/_define_property");
|
|
12
12
|
const _texteditor = require("@fluentui-copilot/text-editor");
|
|
13
|
+
const _BasicFunctionality = require("../BasicFunctionality");
|
|
13
14
|
class GhostTextPluginBase {
|
|
14
15
|
cleanup() {
|
|
15
16
|
var _this___cleanup, _this;
|
|
@@ -43,12 +44,14 @@ class GhostTextPluginBase {
|
|
|
43
44
|
let ghostTextNodeKey = null;
|
|
44
45
|
let lastText = undefined;
|
|
45
46
|
let justRemovedGhostText = false;
|
|
47
|
+
let justAddedGhostText = false;
|
|
46
48
|
function $clearGhostText() {
|
|
47
49
|
const ghostTextNode = ghostTextNodeKey !== null ? (0, _texteditor.$getNodeByKey)(ghostTextNodeKey) : null;
|
|
48
50
|
ghostTextNodeKey = null;
|
|
49
51
|
lastText = undefined;
|
|
50
52
|
if (ghostTextNode && ghostTextNode.isAttached()) {
|
|
51
53
|
ghostTextNode.remove();
|
|
54
|
+
justRemovedGhostText = true;
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
function handleGhostTextNodeTransform(node) {
|
|
@@ -67,7 +70,6 @@ class GhostTextPluginBase {
|
|
|
67
70
|
$clearGhostText();
|
|
68
71
|
const selection = (0, _texteditor.$getSelection)();
|
|
69
72
|
if (!text || !selection) {
|
|
70
|
-
justRemovedGhostText = true;
|
|
71
73
|
return;
|
|
72
74
|
}
|
|
73
75
|
const selectionCopy = selection.clone();
|
|
@@ -78,18 +80,58 @@ class GhostTextPluginBase {
|
|
|
78
80
|
node
|
|
79
81
|
]);
|
|
80
82
|
(0, _texteditor.$setSelection)(selectionCopy);
|
|
83
|
+
justAddedGhostText = true;
|
|
84
|
+
justRemovedGhostText = false;
|
|
81
85
|
}, {
|
|
82
86
|
tag: 'historic'
|
|
83
87
|
});
|
|
84
88
|
};
|
|
85
89
|
const handleUpdate = (props)=>{
|
|
86
90
|
const { editorState, prevEditorState } = props;
|
|
87
|
-
// If this update was caused by
|
|
88
|
-
if (justRemovedGhostText) {
|
|
91
|
+
// If this update was caused by adding or deleting ghost text, don't recheck the ghost text function until a subsequent update
|
|
92
|
+
if (justRemovedGhostText || justAddedGhostText) {
|
|
89
93
|
justRemovedGhostText = false;
|
|
94
|
+
justAddedGhostText = false;
|
|
90
95
|
return;
|
|
91
96
|
}
|
|
92
97
|
editorState.read(()=>{
|
|
98
|
+
// We only update the ghost text if the user selection is inside the input
|
|
99
|
+
const selection = (0, _texteditor.$getSelection)();
|
|
100
|
+
if (!(0, _texteditor.$getSelection)()) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if ((0, _texteditor.$isRangeSelection)(selection) && selection.isCollapsed()) {
|
|
104
|
+
var _selectedNode_getPreviousSibling;
|
|
105
|
+
var _selection_getNodes_at;
|
|
106
|
+
// All the `$isXNode` functions prefer `null` over `undefined`
|
|
107
|
+
const selectedNode = (_selection_getNodes_at = selection.getNodes().at(0)) !== null && _selection_getNodes_at !== void 0 ? _selection_getNodes_at : null;
|
|
108
|
+
const previousNodeKey = selectedNode === null || selectedNode === void 0 ? void 0 : (_selectedNode_getPreviousSibling = selectedNode.getPreviousSibling()) === null || _selectedNode_getPreviousSibling === void 0 ? void 0 : _selectedNode_getPreviousSibling.getKey();
|
|
109
|
+
const previousNodeIsGhostText = previousNodeKey === ghostTextNodeKey;
|
|
110
|
+
// If the ghost text is active and we're navigating past it, act as if the ghost text is not there and move 1 extra character
|
|
111
|
+
if (previousNodeIsGhostText && !(0, _BasicFunctionality.$isSentinelNode)(selectedNode) && (0, _texteditor.$isTextNode)(selectedNode) && selection.anchor.offset === 0) {
|
|
112
|
+
editor.update(()=>{
|
|
113
|
+
const selection = (0, _texteditor.$getSelection)();
|
|
114
|
+
if ((0, _texteditor.$isRangeSelection)(selection)) {
|
|
115
|
+
selection.modify('move', false, 'character');
|
|
116
|
+
}
|
|
117
|
+
$clearGhostText();
|
|
118
|
+
}, {
|
|
119
|
+
tag: 'historic'
|
|
120
|
+
});
|
|
121
|
+
// Defer checking the ghost text until after this update has finished
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// If the ghost text is the last node before the sentinel, we shouldn't let selection get past it
|
|
125
|
+
if (previousNodeIsGhostText && (0, _BasicFunctionality.$isSentinelNode)(selectedNode)) {
|
|
126
|
+
editor.update(()=>{
|
|
127
|
+
var _$getNodeByKey_getPreviousSibling, _$getNodeByKey;
|
|
128
|
+
(_$getNodeByKey = (0, _texteditor.$getNodeByKey)(previousNodeKey)) === null || _$getNodeByKey === void 0 ? void 0 : (_$getNodeByKey_getPreviousSibling = _$getNodeByKey.getPreviousSibling()) === null || _$getNodeByKey_getPreviousSibling === void 0 ? void 0 : _$getNodeByKey_getPreviousSibling.selectEnd();
|
|
129
|
+
}, {
|
|
130
|
+
tag: 'historic'
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
93
135
|
const promise = this.__$getGhostText(editor, editorState, prevEditorState);
|
|
94
136
|
promise.then(handleGhostTextResponse).catch((e)=>console.error(e));
|
|
95
137
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["GhostText.base.ts"],"sourcesContent":["import type { EditorState, Klass, LexicalEditor, LexicalNode, UpdateListener } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getNodeByKey,\n $getSelection,\n $setSelection,\n COMMAND_PRIORITY_LOW,\n KEY_TAB_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\n\nexport type GetGhostTextFunction = (\n editor: LexicalEditor,\n editorState: EditorState,\n prevEditorState: EditorState,\n) => Promise<string | undefined>;\n\nexport interface IGhostTextNode<ComponentPropsType> extends LexicalNode {\n __content: string;\n __id: string;\n __allowCommitting?: boolean;\n __componentProps?: ComponentPropsType;\n __exposeText?: boolean;\n}\n\nexport class GhostTextPluginBase<ComponentPropsType> {\n private __id: string;\n private __$getGhostText: GetGhostTextFunction;\n private __componentProps?: ComponentPropsType;\n private __exposeText?: boolean;\n private __allowCompletion?: boolean;\n\n private __cleanup?: () => void;\n\n cleanup() {\n this.__cleanup?.();\n }\n\n constructor(\n editor: LexicalEditor,\n id: string,\n $getGhostText: GetGhostTextFunction,\n nodeClass: Klass<IGhostTextNode<ComponentPropsType>>,\n createNode: (\n id: string,\n content: string,\n exposeText?: boolean,\n componentProps?: ComponentPropsType,\n ) => IGhostTextNode<ComponentPropsType>,\n componentProps?: ComponentPropsType,\n // Whether or not the ghost text should count as text inside the input for submitting and character count\n exposeText?: boolean,\n allowCompletion?: boolean,\n ) {\n this.__id = id;\n this.__$getGhostText = $getGhostText;\n this.__componentProps = componentProps;\n this.__exposeText = exposeText;\n this.__allowCompletion = allowCompletion;\n\n let ghostTextNodeKey: string | null = null;\n let lastText: string | undefined = undefined;\n let justRemovedGhostText = false;\n\n function $clearGhostText() {\n const ghostTextNode = ghostTextNodeKey !== null ? $getNodeByKey(ghostTextNodeKey) : null;\n ghostTextNodeKey = null;\n lastText = undefined;\n if (ghostTextNode && ghostTextNode.isAttached()) {\n ghostTextNode.remove();\n }\n }\n function handleGhostTextNodeTransform(node: IGhostTextNode<ComponentPropsType>) {\n const key = node.getKey();\n\n if (node.__id === id && key !== ghostTextNodeKey) {\n // Only one ghost text\n node.remove();\n $clearGhostText();\n }\n }\n\n const handleGhostTextResponse = (text?: string) => {\n if (text === lastText) {\n return;\n }\n\n editor.update(\n () => {\n $clearGhostText();\n\n const selection = $getSelection();\n if (!text || !selection) {\n justRemovedGhostText = true;\n return;\n }\n\n const selectionCopy = selection.clone();\n\n const node = createNode(this.__id, text, this.__exposeText, this.__componentProps);\n ghostTextNodeKey = node.getKey();\n lastText = text;\n\n selection.insertNodes([node]);\n $setSelection(selectionCopy);\n },\n { tag: 'historic' },\n );\n };\n\n const handleUpdate: UpdateListener = props => {\n const { editorState, prevEditorState } = props;\n // If this update was caused by deleting the previous ghost text, don't create a new one until a subsequent update\n if (justRemovedGhostText) {\n justRemovedGhostText = false;\n return;\n }\n editorState.read(() => {\n const promise = this.__$getGhostText(editor, editorState, prevEditorState);\n promise.then(handleGhostTextResponse).catch(e => console.error(e));\n });\n };\n\n function unmountGhostText() {\n if (ghostTextNodeKey) {\n editor.update(\n () => {\n $clearGhostText();\n },\n { tag: 'historic' },\n );\n }\n }\n\n function $handleTabCommand(e: KeyboardEvent) {\n if (ghostTextNodeKey === null || lastText === null) {\n return false;\n }\n\n const ghostTextNode = $getNodeByKey(ghostTextNodeKey);\n if (!ghostTextNode) {\n return false;\n }\n\n e.preventDefault();\n\n const textNode = $createTextNode(lastText);\n ghostTextNode.replace(textNode);\n textNode.selectEnd();\n $clearGhostText();\n return true;\n }\n\n this.__cleanup = mergeRegister(\n editor.registerNodeTransform(nodeClass, handleGhostTextNodeTransform),\n editor.registerUpdateListener(handleUpdate),\n this.__allowCompletion ? editor.registerCommand(KEY_TAB_COMMAND, $handleTabCommand, COMMAND_PRIORITY_LOW) : noop,\n unmountGhostText,\n );\n }\n\n setExposeText(exposeText?: boolean) {\n this.__exposeText = exposeText;\n }\n\n setComponentProps(componentProps?: ComponentPropsType) {\n this.__componentProps = componentProps;\n }\n\n setGetGhostText($getGhostText: GetGhostTextFunction) {\n this.__$getGhostText = $getGhostText;\n }\n\n setAllowCompletion(allowCompletion?: boolean) {\n this.__allowCompletion = allowCompletion;\n }\n}\nfunction noop(): void {\n return;\n}\n"],"names":["GhostTextPluginBase","cleanup","_this","__cleanup","_this___cleanup","call","setExposeText","exposeText","__exposeText","setComponentProps","componentProps","__componentProps","setGetGhostText","$getGhostText","__$getGhostText","setAllowCompletion","allowCompletion","__allowCompletion","constructor","editor","id","nodeClass","createNode","__id","ghostTextNodeKey","undefined","lastText","justRemovedGhostText","$clearGhostText","$getNodeByKey","ghostTextNode","isAttached","remove","handleGhostTextNodeTransform","node","key","text","selection","clone","getKey","selectionCopy","handleUpdate","props","promise","editorState","prevEditorState","then","handleGhostTextResponse","catch","e","console","error","preventDefault","textNode","selectEnd","mergeRegister","registerNodeTransform","registerUpdateListener","registerCommand","KEY_TAB_COMMAND","$handleTabCommand","COMMAND_PRIORITY_LOW","noop","unmountGhostText"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;+BAyBaA;;;eAAAA;;;;4BAhBN;AAgBA,MAAMA;cASXC;6BACEC;2BAAA,AAAAA,CAAAA,QAAA,IAAA,EAAAC,SAAKA,MAAS,QAAAC,oBAAd,KAAA,IAAA,KAAA,IAAAA,gBAAAC,IAAA,CAAAH;;kBA8HFI,UAAcC,EAAoB;YAChC,CAAAC,YAAKA,GAAAA;;sBAGPC,cAAkBC,EAAmC;YACnD,CAAAC,gBAAKA,GAAAA;;oBAGPC,aAAgBC,EAAmC;YACjD,CAAAC,eAAKA,GAAAA;;uBAGPC,eAAmBC,EAAyB;YAC1C,CAAAC,iBAAKA,GAAAA;;gBAxIPC,MACEC,EAAqBC,EACrBA,EAAUP,aACVA,EAAmCQ,SACnCA,EAAoDC,UACpDA,EAKuCZ,cACvCA,2GAIA;cA3BF,EAAAM,eAAA,CAAQO;8BACR,EAAA,IAAA,EAAA,QAAQT,KAAAA;8BACR,EAAA,IAAA,EAAA,mBAAQH,KAAR;8BACA,EAAA,IAAA,EAAA,oBAAA,KAAA;8BACA,EAAA,IAAA,EAAA,gBAAQM,KAAAA;8BAER,EAAA,IAAA,EAAA,qBAAA,KAAA;8BAsBcG,EAAAA,IAAAA,EAAAA,aAAAA,KAAAA;YACZ,CAAAG,IAAKT,GAAAA;YACL,CAAAA,eAAKH,GAAAA;YACL,CAAAA,gBAAiB,GAAGJ;YACpB,CAAAC,YAAKS,GAAAA;YAEL,CAAAA,iBAAIO,GAAAA;YACJA,mBAAmCC;YACnCC,WAAIC;YAEJA,uBAASC;iBACPA;kBACAJ,gBAAAA,qBAAmB,OAAAK,IAAAA,yBAAA,EAAAL,oBAAA;+BACRC;uBACPK;iCACFA,cAAoBC,UAAA,IAAA;8BACtBC,MAAA;;;iBAGAC,6BAAuBC,IAAA;kBAEvBC,MAAID,KAAKX,MAAI;yBACX,KAAAH,MAAAe,QAAsBX,kBAAA;sCACX;2BACXI;;;;wCAKqBQ,CAAAA;yBACrBV,UAAA;;;yBAKEE,CAAAA;;kCAGKQ,IAAAA,yBAASC;6BACZV,CAAAA,WAAAA;2CACA;;;sCAKWL,UAAWgB,KAAKf;6BAC7BC,WAAAA,IAAmBU,CAAAA,IAAKK,EAAAA,MAAM,IAAA,CAAA/B,YAAA,EAAA,IAAA,CAAAG,gBAAA;mCACnByB,KAAAA,MAAAA;2BAEXC;qCAAuBH,CAAAA;oBAAAA;iBAAAA;6CAAK,EAAAM;;qBAE9B;;;cAGJC,eAAAC,CAAAA;kBAEA,aACQ,iBACN;8HAEyB;sCACvB;uCACF;;;wBAGEC,IAAAA,CAAAA;sBACFA,UAAA,IAAA,CAAA7B,eAAA,CAAAK,QAAAyB,aAAAC;gBACFF,QAAAG,IAAA,CAAAC,yBAAAC,KAAA,CAAAC,CAAAA,IAAAC,QAAAC,KAAA,CAAAF;;;;kCAMQrB;6BAEF,CAAA;;;yBAEJ;gBACF;;;mCAIWqB,CAAA;qCACT,QAAAvB,aAAA,MAAA;uBAEA;;kCAESG,IAAAA,yBAAA,EAAAL;gCACT;uBAEE4B;;4BAGFtB;kBACAuB,WAASC,IAAAA,2BAAS,EAAA5B;0BAClBE,OAAAA,CAAAA;qBACA0B,SAAO;;mBAGJnD;QAMP;QAiBF,IAAA,CAAAA,SAAA,GAAAoD,IAAAA,yBAAA,EAAApC,OAAAqC,qBAAA,CAAAnC,WAAAY,+BAAAd,OAAAsC,sBAAA,CAAAhB,eAAA,IAAA,CAAAxB,iBAAA,GAAAE,OAAAuC,eAAA,CAAAC,2BAAA,EAAAC,mBAAAC,gCAAA,IAAAC,MAAAC;IACA;;AAEA,SAAAD"}
|
|
1
|
+
{"version":3,"sources":["GhostText.base.ts"],"sourcesContent":["import type { EditorState, Klass, LexicalEditor, LexicalNode, UpdateListener } from '@fluentui-copilot/text-editor';\nimport {\n $createTextNode,\n $getNodeByKey,\n $getSelection,\n $isRangeSelection,\n $isTextNode,\n $setSelection,\n COMMAND_PRIORITY_LOW,\n KEY_TAB_COMMAND,\n mergeRegister,\n} from '@fluentui-copilot/text-editor';\nimport { $isSentinelNode } from '../BasicFunctionality';\n\nexport type GetGhostTextFunction = (\n editor: LexicalEditor,\n editorState: EditorState,\n prevEditorState: EditorState,\n) => Promise<string | undefined>;\n\nexport interface IGhostTextNode<ComponentPropsType> extends LexicalNode {\n __content: string;\n __id: string;\n __allowCommitting?: boolean;\n __componentProps?: ComponentPropsType;\n __exposeText?: boolean;\n}\n\nexport class GhostTextPluginBase<ComponentPropsType> {\n private __id: string;\n private __$getGhostText: GetGhostTextFunction;\n private __componentProps?: ComponentPropsType;\n private __exposeText?: boolean;\n private __allowCompletion?: boolean;\n\n private __cleanup?: () => void;\n\n cleanup() {\n this.__cleanup?.();\n }\n\n constructor(\n editor: LexicalEditor,\n id: string,\n $getGhostText: GetGhostTextFunction,\n nodeClass: Klass<IGhostTextNode<ComponentPropsType>>,\n createNode: (\n id: string,\n content: string,\n exposeText?: boolean,\n componentProps?: ComponentPropsType,\n ) => IGhostTextNode<ComponentPropsType>,\n componentProps?: ComponentPropsType,\n // Whether or not the ghost text should count as text inside the input for submitting and character count\n exposeText?: boolean,\n allowCompletion?: boolean,\n ) {\n this.__id = id;\n this.__$getGhostText = $getGhostText;\n this.__componentProps = componentProps;\n this.__exposeText = exposeText;\n this.__allowCompletion = allowCompletion;\n\n let ghostTextNodeKey: string | null = null;\n let lastText: string | undefined = undefined;\n let justRemovedGhostText = false;\n let justAddedGhostText = false;\n\n function $clearGhostText() {\n const ghostTextNode = ghostTextNodeKey !== null ? $getNodeByKey(ghostTextNodeKey) : null;\n ghostTextNodeKey = null;\n lastText = undefined;\n if (ghostTextNode && ghostTextNode.isAttached()) {\n ghostTextNode.remove();\n justRemovedGhostText = true;\n }\n }\n function handleGhostTextNodeTransform(node: IGhostTextNode<ComponentPropsType>) {\n const key = node.getKey();\n\n if (node.__id === id && key !== ghostTextNodeKey) {\n // Only one ghost text\n node.remove();\n $clearGhostText();\n }\n }\n\n const handleGhostTextResponse = (text?: string) => {\n if (text === lastText) {\n return;\n }\n\n editor.update(\n () => {\n $clearGhostText();\n\n const selection = $getSelection();\n if (!text || !selection) {\n return;\n }\n\n const selectionCopy = selection.clone();\n\n const node = createNode(this.__id, text, this.__exposeText, this.__componentProps);\n ghostTextNodeKey = node.getKey();\n lastText = text;\n\n selection.insertNodes([node]);\n $setSelection(selectionCopy);\n justAddedGhostText = true;\n justRemovedGhostText = false;\n },\n { tag: 'historic' },\n );\n };\n\n const handleUpdate: UpdateListener = props => {\n const { editorState, prevEditorState } = props;\n\n // If this update was caused by adding or deleting ghost text, don't recheck the ghost text function until a subsequent update\n if (justRemovedGhostText || justAddedGhostText) {\n justRemovedGhostText = false;\n justAddedGhostText = false;\n return;\n }\n\n editorState.read(() => {\n // We only update the ghost text if the user selection is inside the input\n const selection = $getSelection();\n if (!$getSelection()) {\n return;\n }\n\n if ($isRangeSelection(selection) && selection.isCollapsed()) {\n // All the `$isXNode` functions prefer `null` over `undefined`\n const selectedNode = selection.getNodes().at(0) ?? null;\n const previousNodeKey = selectedNode?.getPreviousSibling()?.getKey();\n const previousNodeIsGhostText = previousNodeKey === ghostTextNodeKey;\n // If the ghost text is active and we're navigating past it, act as if the ghost text is not there and move 1 extra character\n if (\n previousNodeIsGhostText &&\n !$isSentinelNode(selectedNode) &&\n $isTextNode(selectedNode) &&\n selection.anchor.offset === 0\n ) {\n editor.update(\n () => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n selection.modify('move', false, 'character');\n }\n $clearGhostText();\n },\n { tag: 'historic' },\n );\n // Defer checking the ghost text until after this update has finished\n return;\n }\n\n // If the ghost text is the last node before the sentinel, we shouldn't let selection get past it\n if (previousNodeIsGhostText && $isSentinelNode(selectedNode)) {\n editor.update(\n () => {\n $getNodeByKey(previousNodeKey)?.getPreviousSibling()?.selectEnd();\n },\n { tag: 'historic' },\n );\n return;\n }\n }\n\n const promise = this.__$getGhostText(editor, editorState, prevEditorState);\n promise.then(handleGhostTextResponse).catch(e => console.error(e));\n });\n };\n\n function unmountGhostText() {\n if (ghostTextNodeKey) {\n editor.update(\n () => {\n $clearGhostText();\n },\n { tag: 'historic' },\n );\n }\n }\n\n function $handleTabCommand(e: KeyboardEvent) {\n if (ghostTextNodeKey === null || lastText === null) {\n return false;\n }\n\n const ghostTextNode = $getNodeByKey(ghostTextNodeKey);\n if (!ghostTextNode) {\n return false;\n }\n\n e.preventDefault();\n\n const textNode = $createTextNode(lastText);\n ghostTextNode.replace(textNode);\n textNode.selectEnd();\n $clearGhostText();\n return true;\n }\n\n this.__cleanup = mergeRegister(\n editor.registerNodeTransform(nodeClass, handleGhostTextNodeTransform),\n editor.registerUpdateListener(handleUpdate),\n this.__allowCompletion ? editor.registerCommand(KEY_TAB_COMMAND, $handleTabCommand, COMMAND_PRIORITY_LOW) : noop,\n unmountGhostText,\n );\n }\n\n setExposeText(exposeText?: boolean) {\n this.__exposeText = exposeText;\n }\n\n setComponentProps(componentProps?: ComponentPropsType) {\n this.__componentProps = componentProps;\n }\n\n setGetGhostText($getGhostText: GetGhostTextFunction) {\n this.__$getGhostText = $getGhostText;\n }\n\n setAllowCompletion(allowCompletion?: boolean) {\n this.__allowCompletion = allowCompletion;\n }\n}\nfunction noop(): void {\n return;\n}\n"],"names":["GhostTextPluginBase","cleanup","_this","__cleanup","_this___cleanup","call","setExposeText","exposeText","__exposeText","setComponentProps","componentProps","__componentProps","setGetGhostText","$getGhostText","__$getGhostText","setAllowCompletion","allowCompletion","__allowCompletion","constructor","editor","id","nodeClass","createNode","__id","ghostTextNodeKey","undefined","lastText","justRemovedGhostText","justAddedGhostText","$clearGhostText","$getNodeByKey","ghostTextNode","isAttached","handleGhostTextNodeTransform","node","key","text","selection","clone","getKey","selectionCopy","$setSelection","handleUpdate","props","read","$getSelection","selectedNode","previousNodeIsGhostText","previousNodeKey","_selectedNode_getPreviousSibling","getPreviousSibling","$isSentinelNode","modify","_$getNodeByKey","tag","_$getNodeByKey_getPreviousSibling","selectEnd","promise","editorState","prevEditorState","then","handleGhostTextResponse","catch","e","console","error","preventDefault","textNode","mergeRegister","registerNodeTransform","registerUpdateListener","registerCommand","KEY_TAB_COMMAND","$handleTabCommand","COMMAND_PRIORITY_LOW","noop","unmountGhostText"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "ImperativeControlBase", {
|
|
|
10
10
|
});
|
|
11
11
|
const _define_property = require("@swc/helpers/_/_define_property");
|
|
12
12
|
const _texteditor = require("@fluentui-copilot/text-editor");
|
|
13
|
+
const _BasicFunctionality = require("../BasicFunctionality");
|
|
13
14
|
class ImperativeControlBase {
|
|
14
15
|
moveCursor(location) {
|
|
15
16
|
this.__editor.update(()=>{
|
|
@@ -62,12 +63,13 @@ class ImperativeControlBase {
|
|
|
62
63
|
insertTextAtCursor(text) {
|
|
63
64
|
this.__editor.update(()=>{
|
|
64
65
|
var _$getSelection;
|
|
65
|
-
(_$getSelection = (0, _texteditor.$getSelection)())
|
|
66
|
+
const selection = (_$getSelection = (0, _texteditor.$getSelection)()) !== null && _$getSelection !== void 0 ? _$getSelection : (0, _texteditor.$getRoot)().selectEnd();
|
|
67
|
+
selection.insertText(text);
|
|
66
68
|
});
|
|
67
69
|
}
|
|
68
70
|
getInputText() {
|
|
69
71
|
return this.__editor.getEditorState().read(()=>{
|
|
70
|
-
return (0, _texteditor.$getRoot)().getTextContent();
|
|
72
|
+
return (0, _texteditor.$getRoot)().getTextContent().replace(_BasicFunctionality.SENTINEL_VALUE, '');
|
|
71
73
|
});
|
|
72
74
|
}
|
|
73
75
|
scrollToBottom() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["ImperativeControl.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createParagraphNode,\n $createRangeSelection,\n $createTextNode,\n $getLeafNodes,\n $getRoot,\n $getSelection,\n $isTextNode,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n} from '@fluentui-copilot/text-editor';\n\nexport interface IImperativeControlBase {\n setInputText: (inputText: string) => void;\n appendText: (text: string) => void;\n prependText: (text: string) => void;\n insertTextAtCursor: (text: string) => void;\n getInputText: () => string;\n scrollToBottom: () => void;\n moveCursor: (location: number) => void;\n}\n\nexport class ImperativeControlBase implements IImperativeControlBase {\n private __editor: LexicalEditor;\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n }\n\n moveCursor(location: number): void {\n this.__editor.update(() => {\n const children = $getLeafNodes($getRoot());\n\n let baseOffset = 0;\n let currentNode = children.shift();\n while (baseOffset < location && currentNode) {\n const nodeLength =\n $isTextNode(currentNode) && !currentNode.isToken()\n ? currentNode.getTextContent().length\n : // Token text nodes and non-text nodes are considered to be a single entry in the input\n 1;\n\n if (baseOffset + nodeLength >= location) {\n const elementType = $isTextNode(currentNode) ? 'text' : 'element';\n const localOffset = location - baseOffset;\n const nodeKey = currentNode.getKey();\n\n const selection = $createRangeSelection();\n selection.anchor.set(nodeKey, localOffset, elementType);\n selection.focus.set(nodeKey, localOffset, elementType);\n\n $setSelection($normalizeSelection__EXPERIMENTAL(selection));\n return;\n }\n\n baseOffset += nodeLength;\n currentNode = children.shift();\n }\n\n if (location > baseOffset) {\n $getRoot().selectEnd();\n }\n });\n }\n setInputText(inputText: string) {\n this.__editor.update(() => {\n const root = $getRoot();\n root.clear();\n if (inputText !== '') {\n const newParagraph = $createParagraphNode();\n const newText = $createTextNode(inputText);\n\n newParagraph.append(newText);\n root.append(newParagraph);\n root.selectEnd();\n }\n });\n }\n appendText(text: string) {\n this.__editor.update(() => {\n $getRoot().selectEnd().insertText(text);\n });\n }\n prependText(text: string) {\n this.__editor.update(() => {\n $getRoot().selectStart().insertText(text);\n });\n }\n insertTextAtCursor(text: string) {\n this.__editor.update(() => {\n $getSelection()
|
|
1
|
+
{"version":3,"sources":["ImperativeControl.base.ts"],"sourcesContent":["import type { LexicalEditor } from '@fluentui-copilot/text-editor';\nimport {\n $createParagraphNode,\n $createRangeSelection,\n $createTextNode,\n $getLeafNodes,\n $getRoot,\n $getSelection,\n $isTextNode,\n $normalizeSelection__EXPERIMENTAL,\n $setSelection,\n} from '@fluentui-copilot/text-editor';\nimport { SENTINEL_VALUE } from '../BasicFunctionality';\n\nexport interface IImperativeControlBase {\n setInputText: (inputText: string) => void;\n appendText: (text: string) => void;\n prependText: (text: string) => void;\n insertTextAtCursor: (text: string) => void;\n getInputText: () => string;\n scrollToBottom: () => void;\n moveCursor: (location: number) => void;\n}\n\nexport class ImperativeControlBase implements IImperativeControlBase {\n private __editor: LexicalEditor;\n\n constructor(editor: LexicalEditor) {\n this.__editor = editor;\n }\n\n moveCursor(location: number): void {\n this.__editor.update(() => {\n const children = $getLeafNodes($getRoot());\n\n let baseOffset = 0;\n let currentNode = children.shift();\n while (baseOffset < location && currentNode) {\n const nodeLength =\n $isTextNode(currentNode) && !currentNode.isToken()\n ? currentNode.getTextContent().length\n : // Token text nodes and non-text nodes are considered to be a single entry in the input\n 1;\n\n if (baseOffset + nodeLength >= location) {\n const elementType = $isTextNode(currentNode) ? 'text' : 'element';\n const localOffset = location - baseOffset;\n const nodeKey = currentNode.getKey();\n\n const selection = $createRangeSelection();\n selection.anchor.set(nodeKey, localOffset, elementType);\n selection.focus.set(nodeKey, localOffset, elementType);\n\n $setSelection($normalizeSelection__EXPERIMENTAL(selection));\n return;\n }\n\n baseOffset += nodeLength;\n currentNode = children.shift();\n }\n\n if (location > baseOffset) {\n $getRoot().selectEnd();\n }\n });\n }\n setInputText(inputText: string) {\n this.__editor.update(() => {\n const root = $getRoot();\n root.clear();\n if (inputText !== '') {\n const newParagraph = $createParagraphNode();\n const newText = $createTextNode(inputText);\n\n newParagraph.append(newText);\n root.append(newParagraph);\n root.selectEnd();\n }\n });\n }\n appendText(text: string) {\n this.__editor.update(() => {\n $getRoot().selectEnd().insertText(text);\n });\n }\n prependText(text: string) {\n this.__editor.update(() => {\n $getRoot().selectStart().insertText(text);\n });\n }\n insertTextAtCursor(text: string) {\n this.__editor.update(() => {\n const selection = $getSelection() ?? $getRoot().selectEnd();\n selection.insertText(text);\n });\n }\n getInputText() {\n return this.__editor.getEditorState().read(() => {\n return $getRoot().getTextContent().replace(SENTINEL_VALUE, '');\n });\n }\n scrollToBottom() {\n this.__editor.getRootElement()?.scrollIntoView({ behavior: 'smooth', block: 'end' });\n return;\n }\n}\n"],"names":["ImperativeControlBase","moveCursor","location","__editor","update","children","$getLeafNodes","$getRoot","baseOffset","currentNode","nodeLength","$isTextNode","isToken","getTextContent","length","elementType","localOffset","nodeKey","selection","$createRangeSelection","anchor","focus","$setSelection","$normalizeSelection__EXPERIMENTAL","selectEnd","setInputText","inputText","root","newParagraph","$createParagraphNode","newText","$createTextNode","appendText","text","insertText","prependText","selectStart","insertTextAtCursor","$getSelection","getInputText","getEditorState","read","replace","SENTINEL_VALUE","scrollToBottom","getRootElement","_this___editor_getRootElement","scrollIntoView","behavior","block","constructor","editor"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;+BAwBaA;;;eAAAA;;;;4BAbN;oCACwB;AAYxB,MAAMA;eAOXC,QAAWC,EAAgB;YACzB,CAAAC,QAAKA,CAAAA,MAASC,CAAAA;kBACZC,WAAMA,IAAAA,yBAAWC,EAAAA,IAAAA,oBAAcC;6BAE3BC;8BACAC,SAAcJ,KAAAA;kBAClBG,aAAOA,YAAaN,YAAYO;mCACxBC,IAAAA,uBACJC,EAAAA,gBAAYF,CAAAA,YAAiBA,OAAAA,KAAYG,YACrCH,cAAYI,GAAAA,MAAc,GAAGC;iCAI/BN,cAAaE,UAAcR;wCACvBa,IAAAA,uBAAcJ,EAAAA,eAAYF,SAAAA;wCAC1BO,WAAcd;oCACde,YAAUR,MAAAA;sCAEVS,IAAAA,iCAAYC;8BAClBD,MAAAA,CAAAA,GAAUE,CAAAA,SAAUJ,aAAUA;8BAC9BE,KAAAA,CAAAA,GAAAA,CAAUG,SAASL,aAAUA;iDAE7BM,EAAAA,IAAAA,6CAAcC,EAAAA;;;8BAIhBf;8BACAC,SAAcJ,KAAAA;;2BAGZH,YAAWM;wCACbD,IAAAA,SAAWiB;;;;iBAIjBC,SAAaC,EAAiB;YAC5B,CAAAvB,QAAKA,CAAAA,MAASC,CAAAA;kBACZuB,OAAMA,IAAAA,oBAAOpB;sBACboB;8BACID,IAAAA;qCACIE,IAAAA,gCAAeC;gCACfC,IAAAA,2BAAUC,EAAAA;6BAEhBH,MAAAA,CAAAA;2BACAD,CAAAA;8BACKH;;;;eAIXQ,IAAWC,EAAY;YACrB,CAAA9B,QAAKA,CAAAA,MAASC,CAAAA;oCACZG,IAAAA,SAAWiB,GAAAA,UAAYU,CAAAA;;;gBAG3BC,IAAYF,EAAY;YACtB,CAAA9B,QAAKA,CAAAA,MAASC,CAAAA;oCACZG,IAAAA,WAAW6B,GAAAA,UAAcF,CAAAA;;;uBAG7BG,IAAmBJ,EAAY;YAC7B,CAAA9B,QAAKA,CAAAA,MAASC,CAAAA;;kBACZc,YAAMA,CAAAA,iBAAYoB,IAAAA,yBAAAA,GAAAA,MAAAA,QAAAA,mBAAAA,KAAAA,IAAAA,iBAAAA,IAAAA,oBAAmB/B,IAAAA,SAAWiB;sBAChDN,UAAUgB,CAAAA;;;mBAGdK;eACE,IAAO,CAAApC,QAAKA,CAAAA,cAASqC,GAAAA,IAAiBC,CAAAA;mBACpClC,IAAAA,oBAAOA,IAAAA,cAAWM,GAAAA,OAAiB6B,CAAAA,kCAAQC,EAAAA;;;qBAG/CC;;yCACE,IAAA,CAAAzC,QAAKA,CAAAA,cAAS0C,EAAAA,MAAc,QAAAC,kCAA5B,KAAA,IAAA,KAAA,IAAAA,8BAAAC,cAAgCA,CAAAA;sBAAiBC;mBAAoBC;;;;gBA3EvEC,MAAYC,CAAqB;8BAFjC,EAAA,IAAA,EAAA,YAAQhD,KAAR;YAGE,CAAAA,QAAKA,GAAAA;;AA6ET"}
|
package/lib-commonjs/index.js
CHANGED
|
@@ -30,6 +30,9 @@ _export(exports, {
|
|
|
30
30
|
ManualGhostTextBase: function() {
|
|
31
31
|
return _ManualGhostText.ManualGhostTextBase;
|
|
32
32
|
},
|
|
33
|
+
SENTINEL_VALUE: function() {
|
|
34
|
+
return _BasicFunctionality.SENTINEL_VALUE;
|
|
35
|
+
},
|
|
33
36
|
SentinelNode: function() {
|
|
34
37
|
return _BasicFunctionality.SentinelNode;
|
|
35
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["index.ts"],"sourcesContent":["export {
|
|
1
|
+
{"version":3,"sources":["index.ts"],"sourcesContent":["export {\n BasicFunctionalityBase,\n SentinelNode,\n $createSentinelNode,\n $isSentinelNode,\n SENTINEL_VALUE,\n} from './BasicFunctionality';\nexport type { IBasicFunctionalityBase } from './BasicFunctionality';\n\nexport { ChatInputEntityPluginBase } from './ChatInputEntity';\nexport type {\n ChatInputEntityData,\n ChatInputEntityPluginProps,\n IChatInputEntityPluginBase,\n IEntityNode,\n} from './ChatInputEntity';\n\nexport { GhostTextPluginBase } from './GhostText';\nexport type { GetGhostTextFunction, IGhostTextNode } from './GhostText';\n\nexport { ImperativeControlBase } from './ImperativeControl';\nexport type { IImperativeControlBase } from './ImperativeControl';\n\nexport { ManualGhostTextBase } from './ManualGhostText';\nexport type { IManualGhostTextBase } from './ManualGhostText';\n"],"names":["$createSentinelNode","$isSentinelNode","BasicFunctionalityBase","ChatInputEntityPluginBase","GhostTextPluginBase","ImperativeControlBase","ManualGhostTextBase","SENTINEL_VALUE","SentinelNode"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;;;;;;;;IAGEA,mBAAmB;eAAnBA,uCAAmB;;IACnBC,eAAe;eAAfA,mCAAe;;IAHfC,sBAAsB;eAAtBA,0CAAsB;;IAQfC,yBAAyB;eAAzBA,0CAAyB;;IAQzBC,mBAAmB;eAAnBA,8BAAmB;;IAGnBC,qBAAqB;eAArBA,wCAAqB;;IAGrBC,mBAAmB;eAAnBA,oCAAmB;;IAlB1BC,cAAc;eAAdA,kCAAc;;IAHdC,YAAY;eAAZA,gCAAY;;;oCAIP;iCAGmC;2BAQN;mCAGE;iCAGF"}
|
package/package.json
CHANGED