@atlaskit/editor-plugin-autocomplete 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/afm-cc/tsconfig.json +5 -1
  3. package/afm-jira/tsconfig.json +5 -1
  4. package/afm-products/tsconfig.json +5 -1
  5. package/build/tsconfig.json +24 -0
  6. package/dist/cjs/autocompletePlugin.js +18 -5
  7. package/dist/cjs/autocompletePluginType.js +5 -1
  8. package/dist/cjs/pm-plugins/autocomplete-plugin.js +361 -0
  9. package/dist/cjs/pm-plugins/ghost-text-decoration.js +39 -0
  10. package/dist/cjs/pm-plugins/scoring-pipeline.js +258 -0
  11. package/dist/cjs/pm-plugins/slow-lane-client.js +197 -0
  12. package/dist/cjs/pm-plugins/text-predictor.js +786 -0
  13. package/dist/es2019/autocompletePlugin.js +20 -6
  14. package/dist/es2019/autocompletePluginType.js +1 -0
  15. package/dist/es2019/pm-plugins/autocomplete-plugin.js +360 -0
  16. package/dist/es2019/pm-plugins/ghost-text-decoration.js +33 -0
  17. package/dist/es2019/pm-plugins/scoring-pipeline.js +224 -0
  18. package/dist/es2019/pm-plugins/slow-lane-client.js +154 -0
  19. package/dist/es2019/pm-plugins/text-predictor.js +624 -0
  20. package/dist/esm/autocompletePlugin.js +18 -5
  21. package/dist/esm/autocompletePluginType.js +1 -0
  22. package/dist/esm/pm-plugins/autocomplete-plugin.js +354 -0
  23. package/dist/esm/pm-plugins/ghost-text-decoration.js +33 -0
  24. package/dist/esm/pm-plugins/scoring-pipeline.js +255 -0
  25. package/dist/esm/pm-plugins/slow-lane-client.js +190 -0
  26. package/dist/esm/pm-plugins/text-predictor.js +783 -0
  27. package/dist/types/autocompletePluginType.d.ts +6 -3
  28. package/dist/types/pm-plugins/autocomplete-plugin.d.ts +36 -0
  29. package/dist/types/pm-plugins/ghost-text-decoration.d.ts +7 -0
  30. package/dist/types/pm-plugins/scoring-pipeline.d.ts +33 -0
  31. package/dist/types/pm-plugins/slow-lane-client.d.ts +46 -0
  32. package/dist/types/pm-plugins/text-predictor.d.ts +90 -0
  33. package/dist/types-ts4.5/autocompletePluginType.d.ts +6 -3
  34. package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +36 -0
  35. package/dist/types-ts4.5/pm-plugins/ghost-text-decoration.d.ts +7 -0
  36. package/dist/types-ts4.5/pm-plugins/scoring-pipeline.d.ts +33 -0
  37. package/dist/types-ts4.5/pm-plugins/slow-lane-client.d.ts +46 -0
  38. package/dist/types-ts4.5/pm-plugins/text-predictor.d.ts +90 -0
  39. package/package.json +2 -2
  40. package/src/autocompletePlugin.tsx +25 -5
  41. package/src/autocompletePluginType.ts +14 -3
  42. package/src/pm-plugins/autocomplete-plugin/package.json +15 -0
  43. package/src/pm-plugins/autocomplete-plugin.ts +429 -0
  44. package/src/pm-plugins/ghost-text-decoration.ts +44 -0
  45. package/src/pm-plugins/scoring-pipeline.ts +297 -0
  46. package/src/pm-plugins/slow-lane-client/package.json +15 -0
  47. package/src/pm-plugins/slow-lane-client.ts +220 -0
  48. package/src/pm-plugins/text-predictor/package.json +15 -0
  49. package/src/pm-plugins/text-predictor.ts +771 -0
  50. package/src/pm-plugins/typings.d.ts +12 -0
  51. package/tsconfig.app.json +10 -2
  52. package/tsconfig.json +3 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # @atlaskit/editor-plugin-autocomplete
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`d214555fc7540`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/d214555fc7540) -
8
+ Add editor plugin for AI-powered inline autocomplete
@@ -5,7 +5,11 @@
5
5
  "outDir": "../../../../../confluence/tsDist/@atlaskit__editor-plugin-autocomplete",
6
6
  "rootDir": "../",
7
7
  "composite": true,
8
- "noCheck": true
8
+ "noCheck": true,
9
+ // resolveJsonModule must remain false: this package contains LFS-tracked JSON data files
10
+ // that appear as pointer text (not valid JSON) in CI environments where LFS is skipped.
11
+ // Enabling resolveJsonModule would cause TS to attempt parsing these files and fail.
12
+ "resolveJsonModule": false
9
13
  },
10
14
  "include": [
11
15
  "../src/**/*.ts",
@@ -5,7 +5,11 @@
5
5
  "outDir": "../../../../../jira/tsDist/@atlaskit__editor-plugin-autocomplete/app",
6
6
  "rootDir": "../",
7
7
  "composite": true,
8
- "noCheck": true
8
+ "noCheck": true,
9
+ // resolveJsonModule must remain false: this package contains LFS-tracked JSON data files
10
+ // that appear as pointer text (not valid JSON) in CI environments where LFS is skipped.
11
+ // Enabling resolveJsonModule would cause TS to attempt parsing these files and fail.
12
+ "resolveJsonModule": false
9
13
  },
10
14
  "include": [
11
15
  "../src/**/*.ts",
@@ -5,7 +5,11 @@
5
5
  "outDir": "../../../../../tsDist/@atlaskit__editor-plugin-autocomplete/app",
6
6
  "rootDir": "../",
7
7
  "composite": true,
8
- "noCheck": true
8
+ "noCheck": true,
9
+ // resolveJsonModule must remain false: this package contains LFS-tracked JSON data files
10
+ // that appear as pointer text (not valid JSON) in CI environments where LFS is skipped.
11
+ // Enabling resolveJsonModule would cause TS to attempt parsing these files and fail.
12
+ "resolveJsonModule": false
9
13
  },
10
14
  "include": [
11
15
  "../src/**/*.ts",
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "../tsconfig",
3
+ "compilerOptions": {
4
+ "paths": {},
5
+ // resolveJsonModule must remain false: this package contains LFS-tracked JSON data files
6
+ // that appear as pointer text (not valid JSON) in CI environments where LFS is skipped.
7
+ // We also intentionally do not set "target": "es5" here (unlike the standard generated
8
+ // build tsconfig) because the ESLint require-unicode-regexp rule enforces the 'u' flag
9
+ // on regexes, which requires es6+ target to compile.
10
+ "resolveJsonModule": false
11
+ },
12
+ "include": ["../src/**/*.ts", "../src/**/*.tsx"],
13
+ "exclude": [
14
+ "../src/**/__tests__/*",
15
+ "../src/**/*.test.*",
16
+ "../src/**/test.*",
17
+ "../src/**/examples.*",
18
+ "../src/**/examples/*",
19
+ "../src/**/examples/**/*",
20
+ "../src/**/*.stories.*",
21
+ "../src/**/stories/*",
22
+ "../src/**/stories/**/*"
23
+ ]
24
+ }
@@ -4,11 +4,24 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.autocompletePlugin = void 0;
7
- // This file will contain the autocomplete plugin implementation.
8
- // The full implementation will be added in a follow-up PR.
9
-
10
- var autocompletePlugin = exports.autocompletePlugin = function autocompletePlugin() {
7
+ var _autocompletePlugin = require("./pm-plugins/autocomplete-plugin");
8
+ var autocompletePlugin = exports.autocompletePlugin = function autocompletePlugin(_ref) {
9
+ var options = _ref.config;
11
10
  return {
12
- name: 'autocomplete'
11
+ name: 'autocomplete',
12
+ getSharedState: function getSharedState(editorState) {
13
+ if (!editorState) {
14
+ return undefined;
15
+ }
16
+ return _autocompletePlugin.autocompletePluginKey.getState(editorState);
17
+ },
18
+ pmPlugins: function pmPlugins() {
19
+ return [{
20
+ name: 'autocomplete',
21
+ plugin: function plugin() {
22
+ return (0, _autocompletePlugin.createAutocompletePlugin)(options);
23
+ }
24
+ }];
25
+ }
13
26
  };
14
27
  };
@@ -1 +1,5 @@
1
- "use strict";
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -0,0 +1,361 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.createAutocompletePlugin = exports.autocompletePluginKey = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
10
+ var _keymap = require("@atlaskit/editor-prosemirror/keymap");
11
+ var _state = require("@atlaskit/editor-prosemirror/state");
12
+ var _view = require("@atlaskit/editor-prosemirror/view");
13
+ var _ghostTextDecoration = require("./ghost-text-decoration");
14
+ var _slowLaneClient = require("./slow-lane-client");
15
+ var _textPredictor = require("./text-predictor");
16
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
17
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
18
+ var SLOW_LANE_ENDPOINT = '/gateway/api/v1/autocomplete/typeahead-encodings';
19
+ var autocompletePluginKey = exports.autocompletePluginKey = new _state.PluginKey('autocomplete');
20
+ var DEBOUNCE_MS = 150;
21
+ var createInitialState = function createInitialState() {
22
+ return {
23
+ ghostText: '',
24
+ ghostPosition: -1,
25
+ decorationSet: _view.DecorationSet.empty
26
+ };
27
+ };
28
+
29
+ /**
30
+ * Extract text content before the cursor from the current document.
31
+ * Returns the last ~200 characters for context.
32
+ */
33
+ var getTextBeforeCursor = function getTextBeforeCursor(state) {
34
+ var $from = state.selection.$from;
35
+ var maxChars = 200;
36
+
37
+ // 1. Get the perfectly flattened text of the current block up to the cursor
38
+ var blockNode = $from.parent;
39
+ var offsetInBlock = $from.parentOffset;
40
+ var blockText = blockNode.textContent.slice(0, offsetInBlock);
41
+ if (blockText.length >= maxChars) {
42
+ return blockText.slice(-maxChars);
43
+ }
44
+ var fullText = blockText;
45
+
46
+ // 2. Walk backwards through previous blocks
47
+ var depth = $from.depth - 1;
48
+ while (fullText.length < maxChars && depth >= 0) {
49
+ var parentNode = $from.node(depth);
50
+ var indexInParent = $from.index(depth);
51
+ for (var i = indexInParent - 1; i >= 0 && fullText.length < maxChars; i--) {
52
+ var sibling = parentNode.child(i);
53
+ var siblingText = sibling.textContent;
54
+ fullText = siblingText + '\n' + fullText;
55
+ }
56
+ depth--;
57
+ }
58
+ return fullText.slice(-maxChars);
59
+ };
60
+
61
+ /**
62
+ * Set the autocomplete state via a transaction metadata.
63
+ */
64
+ var setAutocompleteMeta = function setAutocompleteMeta(tr, meta) {
65
+ return tr.setMeta(autocompletePluginKey, meta);
66
+ };
67
+
68
+ /**
69
+ * Apply a ghost text suggestion to the editor state.
70
+ */
71
+ var showGhostText = function showGhostText(view, text, position) {
72
+ var state = view.state,
73
+ dispatch = view.dispatch;
74
+ var decorationSet = (0, _ghostTextDecoration.createGhostTextDecorationSet)(state, position, text);
75
+ var tr = setAutocompleteMeta(state.tr, {
76
+ ghostText: text,
77
+ ghostPosition: position,
78
+ decorationSet: decorationSet
79
+ });
80
+ dispatch(tr);
81
+ };
82
+
83
+ /**
84
+ * Clear the current ghost text from the editor.
85
+ */
86
+ var clearGhostText = function clearGhostText(state, dispatch) {
87
+ var pluginState = autocompletePluginKey.getState(state);
88
+ if (!pluginState || !pluginState.ghostText) {
89
+ return false;
90
+ }
91
+ if (dispatch) {
92
+ var tr = setAutocompleteMeta(state.tr, {
93
+ ghostText: '',
94
+ ghostPosition: -1,
95
+ decorationSet: _view.DecorationSet.empty
96
+ });
97
+ dispatch(tr);
98
+ }
99
+ return true;
100
+ };
101
+
102
+ /**
103
+ * Accept the current ghost text suggestion and insert it into the document.
104
+ */
105
+ var acceptGhostText = function acceptGhostText(state, dispatch) {
106
+ var pluginState = autocompletePluginKey.getState(state);
107
+ if (!pluginState || !pluginState.ghostText) {
108
+ return false;
109
+ }
110
+ if (dispatch) {
111
+ var ghostText = pluginState.ghostText,
112
+ ghostPosition = pluginState.ghostPosition;
113
+ var tr = state.tr.insertText(ghostText, ghostPosition);
114
+ tr = setAutocompleteMeta(tr, {
115
+ ghostText: '',
116
+ ghostPosition: -1,
117
+ decorationSet: _view.DecorationSet.empty
118
+ });
119
+ dispatch(tr);
120
+ }
121
+ return true;
122
+ };
123
+
124
+ /**
125
+ * Context provided to the autocomplete plugin on first editor focus.
126
+ * Text fields are selectively ingested to boost word-frequency scoring for
127
+ * predictions, giving words already present in the document/thread an L1
128
+ * priority boost.
129
+ */
130
+
131
+ /**
132
+ * Build the text payload for the slow-lane request by prepending any available
133
+ * comment context ahead of the live document text. This gives the backend
134
+ * model richer context about the thread the user is writing in.
135
+ */
136
+ var buildSlowLaneText = function buildSlowLaneText(docText, context) {
137
+ var _context$siblingComme, _context$siblingComme2, _context$siblingComme3;
138
+ var lines = [];
139
+ if (context !== null && context !== void 0 && context.parentCommentContent) {
140
+ lines.push("comment: ".concat(context.parentCommentContent));
141
+ }
142
+ context === null || context === void 0 || (_context$siblingComme = context.siblingCommentsContents) === null || _context$siblingComme === void 0 || _context$siblingComme.forEach(function (sibling, index) {
143
+ lines.push("reply ".concat(index + 1, ": ").concat(sibling));
144
+ });
145
+ var nextReplyNumber = ((_context$siblingComme2 = context === null || context === void 0 || (_context$siblingComme3 = context.siblingCommentsContents) === null || _context$siblingComme3 === void 0 ? void 0 : _context$siblingComme3.length) !== null && _context$siblingComme2 !== void 0 ? _context$siblingComme2 : 0) + 1;
146
+ lines.push("reply ".concat(nextReplyNumber, ": ").concat(docText));
147
+ return lines.join('\n');
148
+ };
149
+ var createAutocompletePlugin = exports.createAutocompletePlugin = function createAutocompletePlugin(options) {
150
+ var debounceTimer = null;
151
+ var hasIngestedPage = false;
152
+ var resolvedContext;
153
+ /**
154
+ * Set after accepting a suggestion so the next doc-change update
155
+ * skips scheduling a new prediction for the just-inserted text.
156
+ * Scoped to the factory so multiple editor instances don't share state.
157
+ */
158
+ var justAccepted = false;
159
+
160
+ /**
161
+ * Stores the text-before-cursor snapshot at the moment the user dismissed
162
+ * a suggestion via Escape. While the context remains identical, we suppress
163
+ * re-showing the same suggestion. Resets to null as soon as the text changes.
164
+ */
165
+ var dismissedContext = null;
166
+ var slowLaneClient = (0, _slowLaneClient.createSlowLaneClient)({
167
+ baseUrl: '',
168
+ endpoint: SLOW_LANE_ENDPOINT
169
+ });
170
+ (0, _slowLaneClient.setDefaultSlowLaneClient)(slowLaneClient);
171
+
172
+ /**
173
+ * Schedule a prediction after a short debounce.
174
+ * Tier 1 predictions are synchronous (<0.1ms) but we still debounce
175
+ * to avoid unnecessary work on rapid keystrokes.
176
+ */
177
+ var schedulePrediction = function schedulePrediction(view) {
178
+ if (debounceTimer) {
179
+ clearTimeout(debounceTimer);
180
+ }
181
+ debounceTimer = setTimeout(function () {
182
+ var state = view.state;
183
+ var selection = state.selection;
184
+
185
+ // Only predict for cursor selections (not range selections)
186
+ if (!selection.empty) {
187
+ return;
188
+ }
189
+ var textBefore = getTextBeforeCursor(state);
190
+
191
+ // Suppress re-showing the same suggestion the user just dismissed.
192
+ // Once the text context changes (user types or deletes), this clears automatically.
193
+ if (textBefore === dismissedContext) {
194
+ return;
195
+ }
196
+ dismissedContext = null;
197
+
198
+ // Don't predict if there's not enough context
199
+ if (textBefore.trim().length < 3) {
200
+ return;
201
+ }
202
+
203
+ // Tier 1 prediction is synchronous -- no async needed
204
+ var prediction = (0, _textPredictor.predict)(textBefore);
205
+ if (prediction && prediction.length > 0) {
206
+ showGhostText(view, prediction, selection.from);
207
+ }
208
+ }, DEBOUNCE_MS);
209
+ };
210
+ var maybeUpdateSessionFrequency = function maybeUpdateSessionFrequency(view, prevState) {
211
+ var newText = getTextBeforeCursor(view.state);
212
+ var prevText = getTextBeforeCursor(prevState);
213
+ if (newText.length <= prevText.length) {
214
+ return;
215
+ }
216
+ var lastChar = newText[newText.length - 1];
217
+ if (!/[\t-\r !,\.:;\?\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]/.test(lastChar)) {
218
+ return;
219
+ }
220
+
221
+ // Only fire if the previous state did not already end on a boundary,
222
+ // so we don't double-count when multiple boundary chars are inserted.
223
+ var prevLastChar = prevText[prevText.length - 1];
224
+ if (prevLastChar && /[\t-\r !,\.:;\?\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]/.test(prevLastChar)) {
225
+ return;
226
+ }
227
+ var beforeBoundary = newText.slice(0, -1).trimEnd();
228
+ var lastSpaceIdx = beforeBoundary.lastIndexOf(' ');
229
+ var completedWord = beforeBoundary.slice(lastSpaceIdx + 1).toLowerCase();
230
+ if (completedWord.length >= 2) {
231
+ (0, _textPredictor.incrementSessionFreq)(completedWord);
232
+ }
233
+ };
234
+ return new _safePlugin.SafePlugin({
235
+ key: autocompletePluginKey,
236
+ state: {
237
+ init: function init() {
238
+ return createInitialState();
239
+ },
240
+ apply: function apply(tr, pluginState) {
241
+ var meta = tr.getMeta(autocompletePluginKey);
242
+ if (meta) {
243
+ return _objectSpread(_objectSpread({}, pluginState), meta);
244
+ }
245
+
246
+ // If the document changed, clear the ghost text
247
+ // (new prediction will be scheduled from view.update)
248
+ if (tr.docChanged) {
249
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
250
+ ghostText: '',
251
+ ghostPosition: -1,
252
+ decorationSet: _view.DecorationSet.empty
253
+ });
254
+ }
255
+
256
+ // If selection changed without doc change, clear ghost text
257
+ if (tr.selectionSet && pluginState.ghostText) {
258
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
259
+ ghostText: '',
260
+ ghostPosition: -1,
261
+ decorationSet: _view.DecorationSet.empty
262
+ });
263
+ }
264
+ return pluginState;
265
+ }
266
+ },
267
+ props: {
268
+ decorations: function decorations(state) {
269
+ var _pluginState$decorati;
270
+ var pluginState = autocompletePluginKey.getState(state);
271
+ return (_pluginState$decorati = pluginState === null || pluginState === void 0 ? void 0 : pluginState.decorationSet) !== null && _pluginState$decorati !== void 0 ? _pluginState$decorati : _view.DecorationSet.empty;
272
+ },
273
+ handleKeyDown: (0, _keymap.keydownHandler)({
274
+ Tab: function Tab(state, dispatch) {
275
+ var accepted = acceptGhostText(state, dispatch);
276
+ if (accepted) justAccepted = true;
277
+ return accepted;
278
+ },
279
+ ArrowRight: function ArrowRight(state, dispatch) {
280
+ var accepted = acceptGhostText(state, dispatch);
281
+ if (accepted) justAccepted = true;
282
+ return accepted;
283
+ },
284
+ Escape: function Escape(state, dispatch) {
285
+ var didClear = clearGhostText(state, dispatch);
286
+ if (didClear) {
287
+ dismissedContext = getTextBeforeCursor(state);
288
+ }
289
+ return didClear;
290
+ }
291
+ }),
292
+ handleDOMEvents: {
293
+ blur: function blur(view) {
294
+ var pluginState = autocompletePluginKey.getState(view.state);
295
+ if (pluginState !== null && pluginState !== void 0 && pluginState.ghostText) {
296
+ clearGhostText(view.state, view.dispatch);
297
+ }
298
+ return false;
299
+ },
300
+ focus: function focus() {
301
+ (0, _textPredictor.loadDefaultVocabulary)();
302
+ (0, _textPredictor.loadVectorsAsync)().catch(function () {});
303
+ if (!hasIngestedPage) {
304
+ hasIngestedPage = true;
305
+ if (options !== null && options !== void 0 && options.getContext) {
306
+ options.getContext().then(function (context) {
307
+ var _context$siblingComme4;
308
+ if (!context) {
309
+ return;
310
+ }
311
+ resolvedContext = context;
312
+ if (context.fullPageContent) {
313
+ (0, _textPredictor.ingestDocumentPage)(context.fullPageContent);
314
+ }
315
+ if (context.parentCommentContent) {
316
+ (0, _textPredictor.ingestDocumentPage)(context.parentCommentContent);
317
+ }
318
+ (_context$siblingComme4 = context.siblingCommentsContents) === null || _context$siblingComme4 === void 0 || _context$siblingComme4.forEach(_textPredictor.ingestDocumentPage);
319
+ }).catch(function () {});
320
+ }
321
+ }
322
+ return false;
323
+ }
324
+ }
325
+ },
326
+ view: function view() {
327
+ return {
328
+ update: function update(view, prevState) {
329
+ if (!prevState.doc.eq(view.state.doc)) {
330
+ if (justAccepted) {
331
+ justAccepted = false;
332
+
333
+ // ✨ THE FIX: Memorize the text state right after acceptance.
334
+ // Any follow-up transactions will hit the 'dismissedContext'
335
+ // block and abort until the user actually types a new character!
336
+ dismissedContext = getTextBeforeCursor(view.state);
337
+
338
+ // Also clear any pending debounce timers from before the acceptance
339
+ if (debounceTimer) {
340
+ clearTimeout(debounceTimer);
341
+ }
342
+ return;
343
+ }
344
+ maybeUpdateSessionFrequency(view, prevState);
345
+ var textBefore = getTextBeforeCursor(view.state);
346
+ if ((0, _slowLaneClient.isWordBoundary)(textBefore)) {
347
+ slowLaneClient.updateContext(buildSlowLaneText(view.state.doc.textContent, resolvedContext));
348
+ }
349
+ schedulePrediction(view);
350
+ }
351
+ },
352
+ destroy: function destroy() {
353
+ if (debounceTimer) {
354
+ clearTimeout(debounceTimer);
355
+ }
356
+ (0, _slowLaneClient.setDefaultSlowLaneClient)(null);
357
+ }
358
+ };
359
+ }
360
+ });
361
+ };
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createGhostTextDecorationSet = void 0;
7
+ var _view = require("@atlaskit/editor-prosemirror/view");
8
+ var GHOST_TEXT_CLASS = 'autocomplete-ghost-text';
9
+
10
+ /**
11
+ * Creates a DecorationSet containing a ghost text widget at the given position.
12
+ * The ghost text is rendered as a styled <span> that appears after the cursor.
13
+ */
14
+ var createGhostTextDecorationSet = exports.createGhostTextDecorationSet = function createGhostTextDecorationSet(state, position, text) {
15
+ if (!text) {
16
+ return _view.DecorationSet.empty;
17
+ }
18
+ var decoration = _view.Decoration.widget(position, function () {
19
+ var container = document.createElement('span');
20
+ container.className = GHOST_TEXT_CLASS;
21
+ container.setAttribute('data-autocomplete-ghost', 'true');
22
+ container.style.color = '#999';
23
+ container.style.opacity = '0.6';
24
+ container.style.pointerEvents = 'none';
25
+ container.style.userSelect = 'none';
26
+ container.style.fontStyle = 'italic';
27
+ // U+200B (Zero Width Space) gives the browser a line-break opportunity
28
+ // immediately before the ghost text. This ensures the typed text before
29
+ // the span is never pushed to the next line by the ghost text's width —
30
+ // only the ghost text itself will wrap if it doesn't fit.
31
+ container.textContent = "\u200B" + text;
32
+ return container;
33
+ }, {
34
+ side: 1,
35
+ // Render after content at this position
36
+ key: 'autocomplete-ghost-text'
37
+ });
38
+ return _view.DecorationSet.create(state.doc, [decoration]);
39
+ };