@atlaskit/editor-plugin-autocomplete 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/cjs/pm-plugins/autocomplete-plugin.js +6 -22
- package/dist/cjs/pm-plugins/scoring-pipeline.js +5 -4
- package/dist/cjs/pm-plugins/text-predictor.js +152 -60
- package/dist/es2019/pm-plugins/autocomplete-plugin.js +6 -22
- package/dist/es2019/pm-plugins/scoring-pipeline.js +5 -4
- package/dist/es2019/pm-plugins/text-predictor.js +105 -50
- package/dist/esm/pm-plugins/autocomplete-plugin.js +6 -22
- package/dist/esm/pm-plugins/scoring-pipeline.js +5 -4
- package/dist/esm/pm-plugins/text-predictor.js +144 -60
- package/dist/types/pm-plugins/text-predictor.d.ts +1 -1
- package/dist/types-ts4.5/pm-plugins/text-predictor.d.ts +1 -1
- package/package.json +2 -2
- package/src/pm-plugins/autocomplete-plugin.ts +6 -16
- package/src/pm-plugins/data/word_index_10k.json +7761 -7759
- package/src/pm-plugins/scoring-pipeline.ts +4 -5
- package/src/pm-plugins/text-predictor.ts +124 -45
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-autocomplete
|
|
2
2
|
|
|
3
|
+
## 3.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`f003833231999`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f003833231999) -
|
|
8
|
+
Code-split the autocomplete vocabulary, L3 word list and word-index JSON via dynamic import so
|
|
9
|
+
they load on autocomplete initialisation instead of being bundled into the editor's main chunk.
|
|
10
|
+
|
|
3
11
|
## 3.0.0
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -38,8 +38,6 @@ var createInitialState = function createInitialState() {
|
|
|
38
38
|
var getTextBeforeCursor = function getTextBeforeCursor(state) {
|
|
39
39
|
var $from = state.selection.$from;
|
|
40
40
|
var maxChars = 200;
|
|
41
|
-
|
|
42
|
-
// 1. Get the perfectly flattened text of the current block up to the cursor
|
|
43
41
|
var blockNode = $from.parent;
|
|
44
42
|
var offsetInBlock = $from.parentOffset;
|
|
45
43
|
var blockText = blockNode.textContent.slice(0, offsetInBlock);
|
|
@@ -48,7 +46,7 @@ var getTextBeforeCursor = function getTextBeforeCursor(state) {
|
|
|
48
46
|
}
|
|
49
47
|
var fullText = blockText;
|
|
50
48
|
|
|
51
|
-
//
|
|
49
|
+
// Walk backwards through previous blocks until we have enough context.
|
|
52
50
|
var depth = $from.depth - 1;
|
|
53
51
|
while (fullText.length < maxChars && depth >= 0) {
|
|
54
52
|
var parentNode = $from.node(depth);
|
|
@@ -249,8 +247,6 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
249
247
|
try {
|
|
250
248
|
var state = view.state;
|
|
251
249
|
var selection = state.selection;
|
|
252
|
-
|
|
253
|
-
// Only predict for cursor selections (not range selections)
|
|
254
250
|
if (!selection.empty) {
|
|
255
251
|
return;
|
|
256
252
|
}
|
|
@@ -262,13 +258,9 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
262
258
|
return;
|
|
263
259
|
}
|
|
264
260
|
dismissedContext = null;
|
|
265
|
-
|
|
266
|
-
// Don't predict if there's not enough context
|
|
267
261
|
if (textBefore.trim().length < 3) {
|
|
268
262
|
return;
|
|
269
263
|
}
|
|
270
|
-
|
|
271
|
-
// Tier 1 prediction is synchronous -- no async needed
|
|
272
264
|
var prediction = (0, _textPredictor.predict)(textBefore);
|
|
273
265
|
if (prediction && prediction.length > 0) {
|
|
274
266
|
var typedLength = getTypedLengthForPrediction(textBefore);
|
|
@@ -330,8 +322,7 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
330
322
|
return _objectSpread(_objectSpread({}, pluginState), meta);
|
|
331
323
|
}
|
|
332
324
|
|
|
333
|
-
//
|
|
334
|
-
// (new prediction will be scheduled from view.update)
|
|
325
|
+
// A new prediction is scheduled from view.update.
|
|
335
326
|
if (tr.docChanged) {
|
|
336
327
|
return _objectSpread(_objectSpread({}, pluginState), {}, {
|
|
337
328
|
ghostText: '',
|
|
@@ -339,8 +330,6 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
339
330
|
decorationSet: _view.DecorationSet.empty
|
|
340
331
|
});
|
|
341
332
|
}
|
|
342
|
-
|
|
343
|
-
// If selection changed without doc change, clear ghost text
|
|
344
333
|
if (tr.selectionSet && pluginState.ghostText) {
|
|
345
334
|
return _objectSpread(_objectSpread({}, pluginState), {}, {
|
|
346
335
|
ghostText: '',
|
|
@@ -401,13 +390,11 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
401
390
|
return false;
|
|
402
391
|
},
|
|
403
392
|
focus: function focus() {
|
|
404
|
-
|
|
405
|
-
(0, _textPredictor.loadDefaultVocabulary)();
|
|
406
|
-
} catch (error) {
|
|
393
|
+
(0, _textPredictor.loadDefaultVocabulary)().catch(function (error) {
|
|
407
394
|
(0, _monitoring.logException)(error, {
|
|
408
395
|
location: 'editor-plugin-autocomplete/loadDefaultVocabulary'
|
|
409
396
|
});
|
|
410
|
-
}
|
|
397
|
+
});
|
|
411
398
|
(0, _textPredictor.loadVectorsAsync)({
|
|
412
399
|
getBinaryUrl: options === null || options === void 0 ? void 0 : options.getVectorsBinaryUrl
|
|
413
400
|
}).catch(function (error) {
|
|
@@ -449,12 +436,9 @@ var createAutocompletePlugin = exports.createAutocompletePlugin = function creat
|
|
|
449
436
|
if (justAccepted) {
|
|
450
437
|
justAccepted = false;
|
|
451
438
|
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
// block and abort until the user actually types a new character!
|
|
439
|
+
// Snapshot the post-acceptance text so follow-up transactions hit
|
|
440
|
+
// the dismissedContext guard and abort until the user types again.
|
|
455
441
|
dismissedContext = getTextBeforeCursor(view.state);
|
|
456
|
-
|
|
457
|
-
// Also clear any pending debounce timers from before the acceptance
|
|
458
442
|
if (debounceTimer) {
|
|
459
443
|
clearTimeout(debounceTimer);
|
|
460
444
|
}
|
|
@@ -166,20 +166,21 @@ function applyGrammarFilter(candidates, previousWord) {
|
|
|
166
166
|
dropped.push(entry.candidate.word);
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
+
|
|
170
|
+
// Grammar is authoritative.
|
|
169
171
|
} catch (err) {
|
|
170
172
|
_iterator2.e(err);
|
|
171
173
|
} finally {
|
|
172
174
|
_iterator2.f();
|
|
173
175
|
}
|
|
174
|
-
var finalFiltered = filtered.length > 0 ? filtered : candidates;
|
|
175
176
|
return {
|
|
176
|
-
filtered:
|
|
177
|
+
filtered: filtered,
|
|
177
178
|
grammarMeta: {
|
|
178
179
|
prevWord: lowerPrev,
|
|
179
180
|
prevTags: prevTags,
|
|
180
181
|
before: candidates.length,
|
|
181
|
-
after:
|
|
182
|
-
dropped:
|
|
182
|
+
after: filtered.length,
|
|
183
|
+
dropped: dropped
|
|
183
184
|
}
|
|
184
185
|
};
|
|
185
186
|
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _typeof3 = require("@babel/runtime/helpers/typeof");
|
|
4
5
|
Object.defineProperty(exports, "__esModule", {
|
|
5
6
|
value: true
|
|
6
7
|
});
|
|
7
8
|
exports.predict = exports.loadVectorsAsync = exports.loadDefaultVocabulary = exports.initVocabulary = exports.initVectors = exports.initL3Vocabulary = exports.ingestDocumentPage = exports.incrementSessionFreq = exports.getPredictorStatus = exports.getLastPredictionDebug = void 0;
|
|
8
9
|
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
9
10
|
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
11
|
+
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
|
|
10
12
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
11
13
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
12
14
|
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
13
15
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
14
16
|
var _ufo = require("../analytics/ufo");
|
|
15
|
-
var _l3_vocabulary = _interopRequireDefault(require("./data/l3_vocabulary.json"));
|
|
16
|
-
var _vocabulary_10k = _interopRequireDefault(require("./data/vocabulary_10k.json"));
|
|
17
|
-
var _word_index_10k = _interopRequireDefault(require("./data/word_index_10k.json"));
|
|
18
17
|
var _debugMode = require("./debug-mode");
|
|
19
18
|
var _scoringPipeline = require("./scoring-pipeline");
|
|
20
19
|
var _slowLaneClient = require("./slow-lane-client");
|
|
20
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof3(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t4 in e) "default" !== _t4 && {}.hasOwnProperty.call(e, _t4) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t4)) && (i.get || i.set) ? o(f, _t4, i) : f[_t4] = e[_t4]); return f; })(e, t); }
|
|
21
21
|
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
22
22
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
23
23
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } /**
|
|
@@ -35,8 +35,9 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
35
35
|
*
|
|
36
36
|
* Session personalization (L1): words the user types are incrementally boosted
|
|
37
37
|
* via incrementSessionFreq(), called on word boundaries from the plugin.
|
|
38
|
-
*/ //
|
|
39
|
-
//
|
|
38
|
+
*/ // The vocabulary, L3 and word-index JSON payloads are dynamically imported in
|
|
39
|
+
// loadDefaultVocabulary / loadVectorsAsync so their (large) contents stay out of
|
|
40
|
+
// the editor's main chunk and only load when autocomplete is initialised.
|
|
40
41
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
41
42
|
|
|
42
43
|
// eslint-disable-next-line require-unicode-regexp
|
|
@@ -44,7 +45,7 @@ var PUNCTUATION_BOUNDARY_REGEX = /^[.,;:!?()\[\]{}"'`]+|[.,;:!?()\[\]{}"'`]+$/g;
|
|
|
44
45
|
var MIN_PREFIX_LENGTH = 3;
|
|
45
46
|
var MAX_CANDIDATES = 200;
|
|
46
47
|
var CONTEXT_WORDS = 10;
|
|
47
|
-
var MIN_SCORE_THRESHOLD = 0.
|
|
48
|
+
var MIN_SCORE_THRESHOLD = 0.35;
|
|
48
49
|
var L3_BASELINE_FREQ = 0.001;
|
|
49
50
|
|
|
50
51
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
@@ -208,7 +209,6 @@ var wordTrie = new WeightedWordTrie();
|
|
|
208
209
|
// L3 Trie (General English Fallback)
|
|
209
210
|
var l3Trie = new WeightedWordTrie();
|
|
210
211
|
|
|
211
|
-
// --- Initialization Function ---
|
|
212
212
|
/**
|
|
213
213
|
* Loads the General English vocabulary.
|
|
214
214
|
* expects a simple array of strings: ["about", "above", "actually", ...]
|
|
@@ -332,14 +332,11 @@ var tokenize = function tokenize(text) {
|
|
|
332
332
|
return tokens;
|
|
333
333
|
};
|
|
334
334
|
var extractPreviousWord = function extractPreviousWord(text) {
|
|
335
|
-
//
|
|
335
|
+
// Only consider the current sentence/line the user is typing in.
|
|
336
336
|
// eslint-disable-next-line require-unicode-regexp
|
|
337
337
|
var sentences = text.split(/[\n.?!]+/);
|
|
338
|
-
|
|
339
|
-
// 2. Only look at the current sentence/line the user is typing in
|
|
340
338
|
var currentSentence = sentences[sentences.length - 1];
|
|
341
339
|
|
|
342
|
-
// 3. Extract the previous word as normal
|
|
343
340
|
// eslint-disable-next-line require-unicode-regexp
|
|
344
341
|
var words = currentSentence.trimEnd().split(/\s+/);
|
|
345
342
|
return words.length >= 2 ? words[words.length - 2] : '';
|
|
@@ -402,7 +399,6 @@ var incrementSessionFreq = exports.incrementSessionFreq = function incrementSess
|
|
|
402
399
|
* Pass `undefined` (or omit the argument) to skip priming — useful when the
|
|
403
400
|
* calling context does not yet have a page value available.
|
|
404
401
|
*/
|
|
405
|
-
// NOTE: We ingest full page context here
|
|
406
402
|
var ingestDocumentPage = exports.ingestDocumentPage = function ingestDocumentPage(pageContent) {
|
|
407
403
|
if (!pageContent) {
|
|
408
404
|
return;
|
|
@@ -435,7 +431,11 @@ var ingestDocumentPage = exports.ingestDocumentPage = function ingestDocumentPag
|
|
|
435
431
|
};
|
|
436
432
|
var predict = exports.predict = function predict(textBefore) {
|
|
437
433
|
if (!isInitialized) {
|
|
438
|
-
|
|
434
|
+
// Vocabulary JSON is code-split and loads asynchronously. Kick off the load
|
|
435
|
+
// and skip this keystroke; the plugin also primes it on focus, so the tries
|
|
436
|
+
// are usually ready before the user types.
|
|
437
|
+
void loadDefaultVocabulary().catch(function () {});
|
|
438
|
+
return null;
|
|
439
439
|
}
|
|
440
440
|
var t0 = performance.now();
|
|
441
441
|
|
|
@@ -487,14 +487,11 @@ var predict = exports.predict = function predict(textBefore) {
|
|
|
487
487
|
if (currentWord.length < MIN_PREFIX_LENGTH) {
|
|
488
488
|
return null;
|
|
489
489
|
}
|
|
490
|
-
|
|
491
|
-
// 1. Primary Query: Ask the L2 Domain Trie
|
|
492
490
|
var candidates = wordTrie.getCandidates(currentWord, MAX_CANDIDATES);
|
|
493
491
|
|
|
494
|
-
//
|
|
492
|
+
// Gap-fill from the L3 general-English trie, requesting a full buffer so
|
|
493
|
+
// enough survive de-duplication against the L2 results.
|
|
495
494
|
if (candidates.length < MAX_CANDIDATES) {
|
|
496
|
-
// Ask L3 for MAX_CANDIDATES to guarantee we have enough buffer
|
|
497
|
-
// to survive the deduplication process.
|
|
498
495
|
var l3Candidates = l3Trie.getCandidates(currentWord, MAX_CANDIDATES);
|
|
499
496
|
var existingWords = new Set(candidates.map(function (c) {
|
|
500
497
|
return c.word;
|
|
@@ -504,8 +501,7 @@ var predict = exports.predict = function predict(textBefore) {
|
|
|
504
501
|
try {
|
|
505
502
|
for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
|
|
506
503
|
var l3c = _step0.value;
|
|
507
|
-
if (candidates.length >= MAX_CANDIDATES) break;
|
|
508
|
-
|
|
504
|
+
if (candidates.length >= MAX_CANDIDATES) break;
|
|
509
505
|
if (!existingWords.has(l3c.word)) {
|
|
510
506
|
candidates.push(l3c);
|
|
511
507
|
}
|
|
@@ -689,9 +685,55 @@ var predict = exports.predict = function predict(textBefore) {
|
|
|
689
685
|
|
|
690
686
|
// ─── Data Loading ────────────────────────────────────────────────────────────
|
|
691
687
|
|
|
688
|
+
/**
|
|
689
|
+
* Unwrap a dynamically imported JSON module to its parsed value, handling both
|
|
690
|
+
* interop modes AFM's bundler chain emits: a `.default`-wrapped namespace
|
|
691
|
+
* (classic webpack) and a named-exports namespace (webpack 5 / atlaspack JSON
|
|
692
|
+
* modules, where `default` can be a misleading scalar). Named exports are
|
|
693
|
+
* preferred when present. The caller declares the JSON `shape` because a dense
|
|
694
|
+
* array and a sparse numeric-keyed object are emitted identically as named
|
|
695
|
+
* exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
|
|
696
|
+
*/
|
|
697
|
+
var unwrapJsonModule = function unwrapJsonModule(mod, shape) {
|
|
698
|
+
if (mod == null || (0, _typeof2.default)(mod) !== 'object') {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
var namespace = mod;
|
|
702
|
+
var ownKeys = Object.keys(namespace).filter(function (k) {
|
|
703
|
+
return k !== 'default' && k !== '__esModule';
|
|
704
|
+
});
|
|
705
|
+
if (ownKeys.length > 0) {
|
|
706
|
+
if (shape === 'array') {
|
|
707
|
+
var len = ownKeys.length;
|
|
708
|
+
var arr = new Array(len);
|
|
709
|
+
for (var i = 0; i < len; i++) {
|
|
710
|
+
arr[i] = namespace[String(i)];
|
|
711
|
+
}
|
|
712
|
+
return arr;
|
|
713
|
+
}
|
|
714
|
+
var obj = {};
|
|
715
|
+
var _iterator1 = _createForOfIteratorHelper(ownKeys),
|
|
716
|
+
_step1;
|
|
717
|
+
try {
|
|
718
|
+
for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
|
|
719
|
+
var k = _step1.value;
|
|
720
|
+
obj[k] = namespace[k];
|
|
721
|
+
}
|
|
722
|
+
} catch (err) {
|
|
723
|
+
_iterator1.e(err);
|
|
724
|
+
} finally {
|
|
725
|
+
_iterator1.f();
|
|
726
|
+
}
|
|
727
|
+
return obj;
|
|
728
|
+
}
|
|
729
|
+
if ('default' in namespace && namespace.default != null) {
|
|
730
|
+
return namespace.default;
|
|
731
|
+
}
|
|
732
|
+
return null;
|
|
733
|
+
};
|
|
692
734
|
var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
|
|
693
735
|
var _ref6 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(options) {
|
|
694
|
-
var url, res, buffer, float32, wordIndex, nWords, dim, _t, _t2;
|
|
736
|
+
var url, _wordIndexOuter$index, res, buffer, float32, wordIndexModule, wordIndexOuter, wordIndex, nWords, dim, _t, _t2;
|
|
695
737
|
return _regenerator.default.wrap(function (_context) {
|
|
696
738
|
while (1) switch (_context.prev = _context.next) {
|
|
697
739
|
case 0:
|
|
@@ -751,9 +793,22 @@ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
751
793
|
return res.arrayBuffer();
|
|
752
794
|
case 9:
|
|
753
795
|
buffer = _context.sent;
|
|
754
|
-
float32 = new Float32Array(buffer);
|
|
755
|
-
|
|
796
|
+
float32 = new Float32Array(buffer); // word_index_10k.json is wrapped as `{ "index": {…} }` so no real entry
|
|
797
|
+
// (e.g. the word "default") can shadow the synthetic ESM `default` export
|
|
798
|
+
// the bundler creates for dynamically-imported JSON.
|
|
799
|
+
_context.next = 10;
|
|
800
|
+
return Promise.resolve().then(function () {
|
|
801
|
+
return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-word-index-10k" */'./data/word_index_10k.json'));
|
|
802
|
+
});
|
|
803
|
+
case 10:
|
|
804
|
+
wordIndexModule = _context.sent;
|
|
805
|
+
wordIndexOuter = unwrapJsonModule(wordIndexModule, 'object');
|
|
806
|
+
wordIndex = (_wordIndexOuter$index = wordIndexOuter === null || wordIndexOuter === void 0 ? void 0 : wordIndexOuter.index) !== null && _wordIndexOuter$index !== void 0 ? _wordIndexOuter$index : {};
|
|
756
807
|
nWords = Object.keys(wordIndex).length;
|
|
808
|
+
if (nWords === 0) {
|
|
809
|
+
// eslint-disable-next-line no-console
|
|
810
|
+
console.warn('[text-predictor] word_index_10k.json missing its `index` wrapper — wordIndex is empty, semantic scoring will be a no-op.');
|
|
811
|
+
}
|
|
757
812
|
dim = float32.length / nWords;
|
|
758
813
|
vectorStore = {
|
|
759
814
|
float32: float32,
|
|
@@ -773,10 +828,10 @@ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
773
828
|
sizeBytes: float32.byteLength
|
|
774
829
|
});
|
|
775
830
|
}
|
|
776
|
-
_context.next =
|
|
831
|
+
_context.next = 12;
|
|
777
832
|
break;
|
|
778
|
-
case
|
|
779
|
-
_context.prev =
|
|
833
|
+
case 11:
|
|
834
|
+
_context.prev = 11;
|
|
780
835
|
_t2 = _context["catch"](6);
|
|
781
836
|
vectorsLoadStarted = false;
|
|
782
837
|
(0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
|
|
@@ -784,11 +839,11 @@ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
784
839
|
});
|
|
785
840
|
// eslint-disable-next-line no-console
|
|
786
841
|
console.warn('[text-predictor] Failed to load vectors:', _t2);
|
|
787
|
-
case
|
|
842
|
+
case 12:
|
|
788
843
|
case "end":
|
|
789
844
|
return _context.stop();
|
|
790
845
|
}
|
|
791
|
-
}, _callee, null, [[3, 5], [6,
|
|
846
|
+
}, _callee, null, [[3, 5], [6, 11]]);
|
|
792
847
|
}));
|
|
793
848
|
return function loadVectorsAsync(_x) {
|
|
794
849
|
return _ref6.apply(this, arguments);
|
|
@@ -797,40 +852,77 @@ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
|
|
|
797
852
|
var initVectors = exports.initVectors = function initVectors(store) {
|
|
798
853
|
vectorStore = store;
|
|
799
854
|
};
|
|
855
|
+
var vocabularyLoadPromise;
|
|
800
856
|
var loadDefaultVocabulary = exports.loadDefaultVocabulary = function loadDefaultVocabulary() {
|
|
801
857
|
if (isInitialized) {
|
|
802
|
-
return;
|
|
858
|
+
return Promise.resolve();
|
|
803
859
|
}
|
|
804
|
-
(
|
|
805
|
-
|
|
806
|
-
// 1. Load the Atlassian Domain (L2)
|
|
807
|
-
var data = _vocabulary_10k.default;
|
|
808
|
-
var terms = Object.entries(data.words).map(function (_ref7) {
|
|
809
|
-
var _ref8 = (0, _slicedToArray2.default)(_ref7, 2),
|
|
810
|
-
word = _ref8[0],
|
|
811
|
-
stats = _ref8[1];
|
|
812
|
-
return {
|
|
813
|
-
word: word,
|
|
814
|
-
freq: stats.freq,
|
|
815
|
-
docFreq: stats.doc_freq,
|
|
816
|
-
authorFreq: stats.author_freq
|
|
817
|
-
};
|
|
818
|
-
});
|
|
819
|
-
initVocabulary({
|
|
820
|
-
terms: terms
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
// 2. Load General English (L3)
|
|
824
|
-
var l3Words = _l3_vocabulary.default;
|
|
825
|
-
initL3Vocabulary(l3Words);
|
|
826
|
-
(0, _ufo.succeedExp)(_ufo.EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
|
|
827
|
-
l2WordCount: terms.length,
|
|
828
|
-
l3WordCount: l3Words.length
|
|
829
|
-
});
|
|
830
|
-
} catch (e) {
|
|
831
|
-
(0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
|
|
832
|
-
errorType: 'parse_error'
|
|
833
|
-
});
|
|
834
|
-
throw e;
|
|
860
|
+
if (vocabularyLoadPromise) {
|
|
861
|
+
return vocabularyLoadPromise;
|
|
835
862
|
}
|
|
863
|
+
vocabularyLoadPromise = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2() {
|
|
864
|
+
var _yield$Promise$all, _yield$Promise$all2, vocabularyModule, l3VocabularyModule, vocabularyData, l3VocabularyData, terms, _t3;
|
|
865
|
+
return _regenerator.default.wrap(function (_context2) {
|
|
866
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
867
|
+
case 0:
|
|
868
|
+
(0, _ufo.startExp)(_ufo.EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton');
|
|
869
|
+
_context2.prev = 1;
|
|
870
|
+
_context2.next = 2;
|
|
871
|
+
return Promise.all([Promise.resolve().then(function () {
|
|
872
|
+
return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-vocabulary-10k" */'./data/vocabulary_10k.json'));
|
|
873
|
+
}), Promise.resolve().then(function () {
|
|
874
|
+
return _interopRequireWildcard(require( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-l3-vocabulary" */'./data/l3_vocabulary.json'));
|
|
875
|
+
})]);
|
|
876
|
+
case 2:
|
|
877
|
+
_yield$Promise$all = _context2.sent;
|
|
878
|
+
_yield$Promise$all2 = (0, _slicedToArray2.default)(_yield$Promise$all, 2);
|
|
879
|
+
vocabularyModule = _yield$Promise$all2[0];
|
|
880
|
+
l3VocabularyModule = _yield$Promise$all2[1];
|
|
881
|
+
vocabularyData = unwrapJsonModule(vocabularyModule, 'object');
|
|
882
|
+
l3VocabularyData = unwrapJsonModule(l3VocabularyModule, 'array');
|
|
883
|
+
if (!((vocabularyData === null || vocabularyData === void 0 ? void 0 : vocabularyData.words) == null || !Array.isArray(l3VocabularyData))) {
|
|
884
|
+
_context2.next = 3;
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
throw new Error('[text-predictor] vocabulary JSON modules could not be unwrapped');
|
|
888
|
+
case 3:
|
|
889
|
+
terms = Object.entries(vocabularyData.words).map(function (_ref8) {
|
|
890
|
+
var _ref9 = (0, _slicedToArray2.default)(_ref8, 2),
|
|
891
|
+
word = _ref9[0],
|
|
892
|
+
stats = _ref9[1];
|
|
893
|
+
return {
|
|
894
|
+
word: word,
|
|
895
|
+
freq: stats.freq,
|
|
896
|
+
docFreq: stats.doc_freq,
|
|
897
|
+
authorFreq: stats.author_freq
|
|
898
|
+
};
|
|
899
|
+
}); // Load L3 before L2: initVocabulary flips `isInitialized = true`, so it
|
|
900
|
+
// must run last — otherwise a throw in initL3Vocabulary would strand
|
|
901
|
+
// `isInitialized` true and the retry path could never reload L3.
|
|
902
|
+
initL3Vocabulary(l3VocabularyData);
|
|
903
|
+
initVocabulary({
|
|
904
|
+
terms: terms
|
|
905
|
+
});
|
|
906
|
+
(0, _ufo.succeedExp)(_ufo.EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
|
|
907
|
+
l2WordCount: terms.length,
|
|
908
|
+
l3WordCount: l3VocabularyData.length
|
|
909
|
+
});
|
|
910
|
+
_context2.next = 5;
|
|
911
|
+
break;
|
|
912
|
+
case 4:
|
|
913
|
+
_context2.prev = 4;
|
|
914
|
+
_t3 = _context2["catch"](1);
|
|
915
|
+
(0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
|
|
916
|
+
errorType: 'parse_error'
|
|
917
|
+
});
|
|
918
|
+
// Allow a later call to retry the load rather than caching the failure.
|
|
919
|
+
vocabularyLoadPromise = undefined;
|
|
920
|
+
throw _t3;
|
|
921
|
+
case 5:
|
|
922
|
+
case "end":
|
|
923
|
+
return _context2.stop();
|
|
924
|
+
}
|
|
925
|
+
}, _callee2, null, [[1, 4]]);
|
|
926
|
+
}))();
|
|
927
|
+
return vocabularyLoadPromise;
|
|
836
928
|
};
|
|
@@ -26,8 +26,6 @@ const getTextBeforeCursor = state => {
|
|
|
26
26
|
$from
|
|
27
27
|
} = state.selection;
|
|
28
28
|
const maxChars = 200;
|
|
29
|
-
|
|
30
|
-
// 1. Get the perfectly flattened text of the current block up to the cursor
|
|
31
29
|
const blockNode = $from.parent;
|
|
32
30
|
const offsetInBlock = $from.parentOffset;
|
|
33
31
|
const blockText = blockNode.textContent.slice(0, offsetInBlock);
|
|
@@ -36,7 +34,7 @@ const getTextBeforeCursor = state => {
|
|
|
36
34
|
}
|
|
37
35
|
let fullText = blockText;
|
|
38
36
|
|
|
39
|
-
//
|
|
37
|
+
// Walk backwards through previous blocks until we have enough context.
|
|
40
38
|
let depth = $from.depth - 1;
|
|
41
39
|
while (fullText.length < maxChars && depth >= 0) {
|
|
42
40
|
const parentNode = $from.node(depth);
|
|
@@ -245,8 +243,6 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
245
243
|
const {
|
|
246
244
|
selection
|
|
247
245
|
} = state;
|
|
248
|
-
|
|
249
|
-
// Only predict for cursor selections (not range selections)
|
|
250
246
|
if (!selection.empty) {
|
|
251
247
|
return;
|
|
252
248
|
}
|
|
@@ -258,13 +254,9 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
258
254
|
return;
|
|
259
255
|
}
|
|
260
256
|
dismissedContext = null;
|
|
261
|
-
|
|
262
|
-
// Don't predict if there's not enough context
|
|
263
257
|
if (textBefore.trim().length < 3) {
|
|
264
258
|
return;
|
|
265
259
|
}
|
|
266
|
-
|
|
267
|
-
// Tier 1 prediction is synchronous -- no async needed
|
|
268
260
|
const prediction = predict(textBefore);
|
|
269
261
|
if (prediction && prediction.length > 0) {
|
|
270
262
|
const typedLength = getTypedLengthForPrediction(textBefore);
|
|
@@ -327,8 +319,7 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
327
319
|
};
|
|
328
320
|
}
|
|
329
321
|
|
|
330
|
-
//
|
|
331
|
-
// (new prediction will be scheduled from view.update)
|
|
322
|
+
// A new prediction is scheduled from view.update.
|
|
332
323
|
if (tr.docChanged) {
|
|
333
324
|
return {
|
|
334
325
|
...pluginState,
|
|
@@ -337,8 +328,6 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
337
328
|
decorationSet: DecorationSet.empty
|
|
338
329
|
};
|
|
339
330
|
}
|
|
340
|
-
|
|
341
|
-
// If selection changed without doc change, clear ghost text
|
|
342
331
|
if (tr.selectionSet && pluginState.ghostText) {
|
|
343
332
|
return {
|
|
344
333
|
...pluginState,
|
|
@@ -400,13 +389,11 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
400
389
|
return false;
|
|
401
390
|
},
|
|
402
391
|
focus: () => {
|
|
403
|
-
|
|
404
|
-
loadDefaultVocabulary();
|
|
405
|
-
} catch (error) {
|
|
392
|
+
loadDefaultVocabulary().catch(error => {
|
|
406
393
|
logException(error, {
|
|
407
394
|
location: 'editor-plugin-autocomplete/loadDefaultVocabulary'
|
|
408
395
|
});
|
|
409
|
-
}
|
|
396
|
+
});
|
|
410
397
|
loadVectorsAsync({
|
|
411
398
|
getBinaryUrl: options === null || options === void 0 ? void 0 : options.getVectorsBinaryUrl
|
|
412
399
|
}).catch(error => {
|
|
@@ -447,12 +434,9 @@ export const createAutocompletePlugin = (options, api) => {
|
|
|
447
434
|
if (justAccepted) {
|
|
448
435
|
justAccepted = false;
|
|
449
436
|
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
// block and abort until the user actually types a new character!
|
|
437
|
+
// Snapshot the post-acceptance text so follow-up transactions hit
|
|
438
|
+
// the dismissedContext guard and abort until the user types again.
|
|
453
439
|
dismissedContext = getTextBeforeCursor(view.state);
|
|
454
|
-
|
|
455
|
-
// Also clear any pending debounce timers from before the acceptance
|
|
456
440
|
if (debounceTimer) {
|
|
457
441
|
clearTimeout(debounceTimer);
|
|
458
442
|
}
|
|
@@ -135,15 +135,16 @@ function applyGrammarFilter(candidates, previousWord) {
|
|
|
135
135
|
dropped.push(entry.candidate.word);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
|
|
139
|
+
// Grammar is authoritative.
|
|
139
140
|
return {
|
|
140
|
-
filtered:
|
|
141
|
+
filtered: filtered,
|
|
141
142
|
grammarMeta: {
|
|
142
143
|
prevWord: lowerPrev,
|
|
143
144
|
prevTags,
|
|
144
145
|
before: candidates.length,
|
|
145
|
-
after:
|
|
146
|
-
dropped:
|
|
146
|
+
after: filtered.length,
|
|
147
|
+
dropped: dropped
|
|
147
148
|
}
|
|
148
149
|
};
|
|
149
150
|
}
|