@atlaskit/editor-plugin-autocomplete 2.5.2 → 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.
@@ -1,4 +1,5 @@
1
1
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
+ import _typeof from "@babel/runtime/helpers/typeof";
2
3
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
4
  import _createClass from "@babel/runtime/helpers/createClass";
4
5
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
@@ -26,11 +27,9 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
26
27
 
27
28
  import { EXPERIENCE_NAME, failExp, startExp, succeedExp } from '../analytics/ufo';
28
29
 
29
- // import bigramsData from './data/bigrams.json';
30
- import l3VocabularyData from './data/l3_vocabulary.json';
31
- import vocabularyData from './data/vocabulary_10k.json';
32
- import wordIndexData from './data/word_index_10k.json';
33
- // import { rankCandidates, isGrammarAllowed } from './scoring-pipeline';
30
+ // The vocabulary, L3 and word-index JSON payloads are dynamically imported in
31
+ // loadDefaultVocabulary / loadVectorsAsync so their (large) contents stay out of
32
+ // the editor's main chunk and only load when autocomplete is initialised.
34
33
  import { isAutocompleteDebugEnabled } from './debug-mode';
35
34
  import { rankCandidates, STAGE1_WEIGHT, STAGE2_WEIGHT, MIN_STAGE1_SCORE } from './scoring-pipeline';
36
35
  import { getStoredContextVector, getStoredLmLogits } from './slow-lane-client';
@@ -42,7 +41,7 @@ var PUNCTUATION_BOUNDARY_REGEX = /^[.,;:!?()\[\]{}"'`]+|[.,;:!?()\[\]{}"'`]+$/g;
42
41
  var MIN_PREFIX_LENGTH = 3;
43
42
  var MAX_CANDIDATES = 200;
44
43
  var CONTEXT_WORDS = 10;
45
- var MIN_SCORE_THRESHOLD = 0.2;
44
+ var MIN_SCORE_THRESHOLD = 0.35;
46
45
  var L3_BASELINE_FREQ = 0.001;
47
46
 
48
47
  // ─── Types ───────────────────────────────────────────────────────────────────
@@ -206,7 +205,6 @@ var wordTrie = new WeightedWordTrie();
206
205
  // L3 Trie (General English Fallback)
207
206
  var l3Trie = new WeightedWordTrie();
208
207
 
209
- // --- Initialization Function ---
210
208
  /**
211
209
  * Loads the General English vocabulary.
212
210
  * expects a simple array of strings: ["about", "above", "actually", ...]
@@ -330,14 +328,11 @@ var tokenize = function tokenize(text) {
330
328
  return tokens;
331
329
  };
332
330
  var extractPreviousWord = function extractPreviousWord(text) {
333
- // 1. Split the text by newlines or punctuation (. ? !)
331
+ // Only consider the current sentence/line the user is typing in.
334
332
  // eslint-disable-next-line require-unicode-regexp
335
333
  var sentences = text.split(/[\n.?!]+/);
336
-
337
- // 2. Only look at the current sentence/line the user is typing in
338
334
  var currentSentence = sentences[sentences.length - 1];
339
335
 
340
- // 3. Extract the previous word as normal
341
336
  // eslint-disable-next-line require-unicode-regexp
342
337
  var words = currentSentence.trimEnd().split(/\s+/);
343
338
  return words.length >= 2 ? words[words.length - 2] : '';
@@ -400,7 +395,6 @@ export var incrementSessionFreq = function incrementSessionFreq(word) {
400
395
  * Pass `undefined` (or omit the argument) to skip priming — useful when the
401
396
  * calling context does not yet have a page value available.
402
397
  */
403
- // NOTE: We ingest full page context here
404
398
  export var ingestDocumentPage = function ingestDocumentPage(pageContent) {
405
399
  if (!pageContent) {
406
400
  return;
@@ -433,7 +427,11 @@ export var ingestDocumentPage = function ingestDocumentPage(pageContent) {
433
427
  };
434
428
  export var predict = function predict(textBefore) {
435
429
  if (!isInitialized) {
436
- loadDefaultVocabulary();
430
+ // Vocabulary JSON is code-split and loads asynchronously. Kick off the load
431
+ // and skip this keystroke; the plugin also primes it on focus, so the tries
432
+ // are usually ready before the user types.
433
+ void loadDefaultVocabulary().catch(function () {});
434
+ return null;
437
435
  }
438
436
  var t0 = performance.now();
439
437
 
@@ -485,14 +483,11 @@ export var predict = function predict(textBefore) {
485
483
  if (currentWord.length < MIN_PREFIX_LENGTH) {
486
484
  return null;
487
485
  }
488
-
489
- // 1. Primary Query: Ask the L2 Domain Trie
490
486
  var candidates = wordTrie.getCandidates(currentWord, MAX_CANDIDATES);
491
487
 
492
- // 2. Fallback Query: Gap-fill with the L3 General English Trie
488
+ // Gap-fill from the L3 general-English trie, requesting a full buffer so
489
+ // enough survive de-duplication against the L2 results.
493
490
  if (candidates.length < MAX_CANDIDATES) {
494
- // Ask L3 for MAX_CANDIDATES to guarantee we have enough buffer
495
- // to survive the deduplication process.
496
491
  var l3Candidates = l3Trie.getCandidates(currentWord, MAX_CANDIDATES);
497
492
  var existingWords = new Set(candidates.map(function (c) {
498
493
  return c.word;
@@ -502,8 +497,7 @@ export var predict = function predict(textBefore) {
502
497
  try {
503
498
  for (_iterator0.s(); !(_step0 = _iterator0.n()).done;) {
504
499
  var l3c = _step0.value;
505
- if (candidates.length >= MAX_CANDIDATES) break; // Stop exactly at the limit
506
-
500
+ if (candidates.length >= MAX_CANDIDATES) break;
507
501
  if (!existingWords.has(l3c.word)) {
508
502
  candidates.push(l3c);
509
503
  }
@@ -687,53 +681,99 @@ export var predict = function predict(textBefore) {
687
681
 
688
682
  // ─── Data Loading ────────────────────────────────────────────────────────────
689
683
 
684
+ /**
685
+ * Unwrap a dynamically imported JSON module to its parsed value, handling both
686
+ * interop modes AFM's bundler chain emits: a `.default`-wrapped namespace
687
+ * (classic webpack) and a named-exports namespace (webpack 5 / atlaspack JSON
688
+ * modules, where `default` can be a misleading scalar). Named exports are
689
+ * preferred when present. The caller declares the JSON `shape` because a dense
690
+ * array and a sparse numeric-keyed object are emitted identically as named
691
+ * exports. Kept in lock-step with the matching helper in local-slow-lane-client.ts.
692
+ */
693
+ var unwrapJsonModule = function unwrapJsonModule(mod, shape) {
694
+ if (mod == null || _typeof(mod) !== 'object') {
695
+ return null;
696
+ }
697
+ var namespace = mod;
698
+ var ownKeys = Object.keys(namespace).filter(function (k) {
699
+ return k !== 'default' && k !== '__esModule';
700
+ });
701
+ if (ownKeys.length > 0) {
702
+ if (shape === 'array') {
703
+ var len = ownKeys.length;
704
+ var arr = new Array(len);
705
+ for (var i = 0; i < len; i++) {
706
+ arr[i] = namespace[String(i)];
707
+ }
708
+ return arr;
709
+ }
710
+ var obj = {};
711
+ var _iterator1 = _createForOfIteratorHelper(ownKeys),
712
+ _step1;
713
+ try {
714
+ for (_iterator1.s(); !(_step1 = _iterator1.n()).done;) {
715
+ var k = _step1.value;
716
+ obj[k] = namespace[k];
717
+ }
718
+ } catch (err) {
719
+ _iterator1.e(err);
720
+ } finally {
721
+ _iterator1.f();
722
+ }
723
+ return obj;
724
+ }
725
+ if ('default' in namespace && namespace.default != null) {
726
+ return namespace.default;
727
+ }
728
+ return null;
729
+ };
690
730
  export var loadVectorsAsync = /*#__PURE__*/function () {
691
731
  var _ref6 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
692
- var url, res, buffer, float32, wordIndex, nWords, dim;
693
- return _regeneratorRuntime.wrap(function _callee$(_context) {
732
+ var url, _wordIndexOuter$index, res, buffer, float32, wordIndexModule, wordIndexOuter, wordIndex, nWords, dim, _t, _t2;
733
+ return _regeneratorRuntime.wrap(function (_context) {
694
734
  while (1) switch (_context.prev = _context.next) {
695
735
  case 0:
696
736
  if (!(vectorStore || vectorsLoadStarted)) {
697
- _context.next = 2;
737
+ _context.next = 1;
698
738
  break;
699
739
  }
700
740
  return _context.abrupt("return");
701
- case 2:
741
+ case 1:
702
742
  if (options !== null && options !== void 0 && options.getBinaryUrl) {
703
- _context.next = 5;
743
+ _context.next = 2;
704
744
  break;
705
745
  }
706
746
  // eslint-disable-next-line no-console
707
747
  console.warn('[text-predictor] loadVectorsAsync called without a getBinaryUrl — vectors will not load. Pass getVectorsBinaryUrl via plugin options.');
708
748
  return _context.abrupt("return");
709
- case 5:
749
+ case 2:
710
750
  vectorsLoadStarted = true;
711
751
  startExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton');
712
- _context.prev = 7;
713
- _context.next = 10;
752
+ _context.prev = 3;
753
+ _context.next = 4;
714
754
  return options.getBinaryUrl();
715
- case 10:
755
+ case 4:
716
756
  url = _context.sent;
717
- _context.next = 19;
757
+ _context.next = 6;
718
758
  break;
719
- case 13:
720
- _context.prev = 13;
721
- _context.t0 = _context["catch"](7);
759
+ case 5:
760
+ _context.prev = 5;
761
+ _t = _context["catch"](3);
722
762
  vectorsLoadStarted = false;
723
763
  failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
724
764
  errorType: 'resolve_url'
725
765
  });
726
766
  // eslint-disable-next-line no-console
727
- console.warn('[text-predictor] Failed to resolve vectors URL:', _context.t0);
767
+ console.warn('[text-predictor] Failed to resolve vectors URL:', _t);
728
768
  return _context.abrupt("return");
729
- case 19:
730
- _context.prev = 19;
731
- _context.next = 22;
769
+ case 6:
770
+ _context.prev = 6;
771
+ _context.next = 7;
732
772
  return fetch(url);
733
- case 22:
773
+ case 7:
734
774
  res = _context.sent;
735
775
  if (res.ok) {
736
- _context.next = 28;
776
+ _context.next = 8;
737
777
  break;
738
778
  }
739
779
  vectorsLoadStarted = false;
@@ -744,14 +784,25 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
744
784
  // eslint-disable-next-line no-console
745
785
  console.warn("[text-predictor] Failed to load vectors: ".concat(res.status));
746
786
  return _context.abrupt("return");
747
- case 28:
748
- _context.next = 30;
787
+ case 8:
788
+ _context.next = 9;
749
789
  return res.arrayBuffer();
750
- case 30:
790
+ case 9:
751
791
  buffer = _context.sent;
752
- float32 = new Float32Array(buffer);
753
- wordIndex = wordIndexData;
792
+ float32 = new Float32Array(buffer); // word_index_10k.json is wrapped as `{ "index": {…} }` so no real entry
793
+ // (e.g. the word "default") can shadow the synthetic ESM `default` export
794
+ // the bundler creates for dynamically-imported JSON.
795
+ _context.next = 10;
796
+ return import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-word-index-10k" */'./data/word_index_10k.json');
797
+ case 10:
798
+ wordIndexModule = _context.sent;
799
+ wordIndexOuter = unwrapJsonModule(wordIndexModule, 'object');
800
+ wordIndex = (_wordIndexOuter$index = wordIndexOuter === null || wordIndexOuter === void 0 ? void 0 : wordIndexOuter.index) !== null && _wordIndexOuter$index !== void 0 ? _wordIndexOuter$index : {};
754
801
  nWords = Object.keys(wordIndex).length;
802
+ if (nWords === 0) {
803
+ // eslint-disable-next-line no-console
804
+ console.warn('[text-predictor] word_index_10k.json missing its `index` wrapper — wordIndex is empty, semantic scoring will be a no-op.');
805
+ }
755
806
  dim = float32.length / nWords;
756
807
  vectorStore = {
757
808
  float32: float32,
@@ -771,22 +822,22 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
771
822
  sizeBytes: float32.byteLength
772
823
  });
773
824
  }
774
- _context.next = 45;
825
+ _context.next = 12;
775
826
  break;
776
- case 40:
777
- _context.prev = 40;
778
- _context.t1 = _context["catch"](19);
827
+ case 11:
828
+ _context.prev = 11;
829
+ _t2 = _context["catch"](6);
779
830
  vectorsLoadStarted = false;
780
831
  failExp(EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
781
832
  errorType: 'network'
782
833
  });
783
834
  // eslint-disable-next-line no-console
784
- console.warn('[text-predictor] Failed to load vectors:', _context.t1);
785
- case 45:
835
+ console.warn('[text-predictor] Failed to load vectors:', _t2);
836
+ case 12:
786
837
  case "end":
787
838
  return _context.stop();
788
839
  }
789
- }, _callee, null, [[7, 13], [19, 40]]);
840
+ }, _callee, null, [[3, 5], [6, 11]]);
790
841
  }));
791
842
  return function loadVectorsAsync(_x) {
792
843
  return _ref6.apply(this, arguments);
@@ -795,40 +846,73 @@ export var loadVectorsAsync = /*#__PURE__*/function () {
795
846
  export var initVectors = function initVectors(store) {
796
847
  vectorStore = store;
797
848
  };
849
+ var vocabularyLoadPromise;
798
850
  export var loadDefaultVocabulary = function loadDefaultVocabulary() {
799
851
  if (isInitialized) {
800
- return;
852
+ return Promise.resolve();
801
853
  }
802
- startExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton');
803
- try {
804
- // 1. Load the Atlassian Domain (L2)
805
- var data = vocabularyData;
806
- var terms = Object.entries(data.words).map(function (_ref7) {
807
- var _ref8 = _slicedToArray(_ref7, 2),
808
- word = _ref8[0],
809
- stats = _ref8[1];
810
- return {
811
- word: word,
812
- freq: stats.freq,
813
- docFreq: stats.doc_freq,
814
- authorFreq: stats.author_freq
815
- };
816
- });
817
- initVocabulary({
818
- terms: terms
819
- });
820
-
821
- // 2. Load General English (L3)
822
- var l3Words = l3VocabularyData;
823
- initL3Vocabulary(l3Words);
824
- succeedExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
825
- l2WordCount: terms.length,
826
- l3WordCount: l3Words.length
827
- });
828
- } catch (e) {
829
- failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
830
- errorType: 'parse_error'
831
- });
832
- throw e;
854
+ if (vocabularyLoadPromise) {
855
+ return vocabularyLoadPromise;
833
856
  }
857
+ vocabularyLoadPromise = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
858
+ var _yield$Promise$all, _yield$Promise$all2, vocabularyModule, l3VocabularyModule, vocabularyData, l3VocabularyData, terms, _t3;
859
+ return _regeneratorRuntime.wrap(function (_context2) {
860
+ while (1) switch (_context2.prev = _context2.next) {
861
+ case 0:
862
+ startExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton');
863
+ _context2.prev = 1;
864
+ _context2.next = 2;
865
+ return Promise.all([import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-vocabulary-10k" */'./data/vocabulary_10k.json'), import( /* webpackChunkName: "@atlaskit-internal_editor-plugin-autocomplete-l3-vocabulary" */'./data/l3_vocabulary.json')]);
866
+ case 2:
867
+ _yield$Promise$all = _context2.sent;
868
+ _yield$Promise$all2 = _slicedToArray(_yield$Promise$all, 2);
869
+ vocabularyModule = _yield$Promise$all2[0];
870
+ l3VocabularyModule = _yield$Promise$all2[1];
871
+ vocabularyData = unwrapJsonModule(vocabularyModule, 'object');
872
+ l3VocabularyData = unwrapJsonModule(l3VocabularyModule, 'array');
873
+ if (!((vocabularyData === null || vocabularyData === void 0 ? void 0 : vocabularyData.words) == null || !Array.isArray(l3VocabularyData))) {
874
+ _context2.next = 3;
875
+ break;
876
+ }
877
+ throw new Error('[text-predictor] vocabulary JSON modules could not be unwrapped');
878
+ case 3:
879
+ terms = Object.entries(vocabularyData.words).map(function (_ref8) {
880
+ var _ref9 = _slicedToArray(_ref8, 2),
881
+ word = _ref9[0],
882
+ stats = _ref9[1];
883
+ return {
884
+ word: word,
885
+ freq: stats.freq,
886
+ docFreq: stats.doc_freq,
887
+ authorFreq: stats.author_freq
888
+ };
889
+ }); // Load L3 before L2: initVocabulary flips `isInitialized = true`, so it
890
+ // must run last — otherwise a throw in initL3Vocabulary would strand
891
+ // `isInitialized` true and the retry path could never reload L3.
892
+ initL3Vocabulary(l3VocabularyData);
893
+ initVocabulary({
894
+ terms: terms
895
+ });
896
+ succeedExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
897
+ l2WordCount: terms.length,
898
+ l3WordCount: l3VocabularyData.length
899
+ });
900
+ _context2.next = 5;
901
+ break;
902
+ case 4:
903
+ _context2.prev = 4;
904
+ _t3 = _context2["catch"](1);
905
+ failExp(EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
906
+ errorType: 'parse_error'
907
+ });
908
+ // Allow a later call to retry the load rather than caching the failure.
909
+ vocabularyLoadPromise = undefined;
910
+ throw _t3;
911
+ case 5:
912
+ case "end":
913
+ return _context2.stop();
914
+ }
915
+ }, _callee2, null, [[1, 4]]);
916
+ }))();
917
+ return vocabularyLoadPromise;
834
918
  };
@@ -84,5 +84,5 @@ export declare const loadVectorsAsync: (options?: {
84
84
  getBinaryUrl?: () => Promise<string>;
85
85
  }) => Promise<void>;
86
86
  export declare const initVectors: (store: VectorStore) => void;
87
- export declare const loadDefaultVocabulary: () => void;
87
+ export declare const loadDefaultVocabulary: () => Promise<void>;
88
88
  export {};
@@ -84,5 +84,5 @@ export declare const loadVectorsAsync: (options?: {
84
84
  getBinaryUrl?: () => Promise<string>;
85
85
  }) => Promise<void>;
86
86
  export declare const initVectors: (store: VectorStore) => void;
87
- export declare const loadDefaultVocabulary: () => void;
87
+ export declare const loadDefaultVocabulary: () => Promise<void>;
88
88
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-autocomplete",
3
- "version": "2.5.2",
3
+ "version": "3.1.0",
4
4
  "description": "Client-side text autocomplete plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -35,8 +35,8 @@
35
35
  "wink-nlp": "^2.4.0"
36
36
  },
37
37
  "peerDependencies": {
38
- "@atlaskit/editor-common": "^114.47.0",
39
- "@atlaskit/editor-plugin-analytics": "^10.1.0",
38
+ "@atlaskit/editor-common": "^115.6.0",
39
+ "@atlaskit/editor-plugin-analytics": "^11.0.0",
40
40
  "react": "^18.2.0"
41
41
  },
42
42
  "techstack": {
@@ -56,7 +56,6 @@ const getTextBeforeCursor = (state: EditorState): string => {
56
56
  const { $from } = state.selection;
57
57
  const maxChars = 200;
58
58
 
59
- // 1. Get the perfectly flattened text of the current block up to the cursor
60
59
  const blockNode = $from.parent;
61
60
  const offsetInBlock = $from.parentOffset;
62
61
  const blockText = blockNode.textContent.slice(0, offsetInBlock);
@@ -67,7 +66,7 @@ const getTextBeforeCursor = (state: EditorState): string => {
67
66
 
68
67
  let fullText = blockText;
69
68
 
70
- // 2. Walk backwards through previous blocks
69
+ // Walk backwards through previous blocks until we have enough context.
71
70
  let depth = $from.depth - 1;
72
71
 
73
72
  while (fullText.length < maxChars && depth >= 0) {
@@ -309,7 +308,6 @@ export const createAutocompletePlugin = (
309
308
  const { state } = view;
310
309
  const { selection } = state;
311
310
 
312
- // Only predict for cursor selections (not range selections)
313
311
  if (!selection.empty) {
314
312
  return;
315
313
  }
@@ -323,12 +321,10 @@ export const createAutocompletePlugin = (
323
321
  }
324
322
  dismissedContext = null;
325
323
 
326
- // Don't predict if there's not enough context
327
324
  if (textBefore.trim().length < 3) {
328
325
  return;
329
326
  }
330
327
 
331
- // Tier 1 prediction is synchronous -- no async needed
332
328
  const prediction = predict(textBefore);
333
329
 
334
330
  if (prediction && prediction.length > 0) {
@@ -402,8 +398,7 @@ export const createAutocompletePlugin = (
402
398
  return { ...pluginState, ...meta };
403
399
  }
404
400
 
405
- // If the document changed, clear the ghost text
406
- // (new prediction will be scheduled from view.update)
401
+ // A new prediction is scheduled from view.update.
407
402
  if (tr.docChanged) {
408
403
  return {
409
404
  ...pluginState,
@@ -413,7 +408,6 @@ export const createAutocompletePlugin = (
413
408
  };
414
409
  }
415
410
 
416
- // If selection changed without doc change, clear ghost text
417
411
  if (tr.selectionSet && pluginState.ghostText) {
418
412
  return {
419
413
  ...pluginState,
@@ -484,13 +478,11 @@ export const createAutocompletePlugin = (
484
478
  return false;
485
479
  },
486
480
  focus: () => {
487
- try {
488
- loadDefaultVocabulary();
489
- } catch (error) {
481
+ loadDefaultVocabulary().catch((error) => {
490
482
  logException(error as Error, {
491
483
  location: 'editor-plugin-autocomplete/loadDefaultVocabulary',
492
484
  });
493
- }
485
+ });
494
486
  loadVectorsAsync({ getBinaryUrl: options?.getVectorsBinaryUrl }).catch((error) => {
495
487
  logException(error as Error, {
496
488
  location: 'editor-plugin-autocomplete/loadVectorsAsync',
@@ -533,12 +525,10 @@ export const createAutocompletePlugin = (
533
525
  if (justAccepted) {
534
526
  justAccepted = false;
535
527
 
536
- // ✨ THE FIX: Memorize the text state right after acceptance.
537
- // Any follow-up transactions will hit the 'dismissedContext'
538
- // block and abort until the user actually types a new character!
528
+ // Snapshot the post-acceptance text so follow-up transactions hit
529
+ // the dismissedContext guard and abort until the user types again.
539
530
  dismissedContext = getTextBeforeCursor(view.state);
540
531
 
541
- // Also clear any pending debounce timers from before the acceptance
542
532
  if (debounceTimer) {
543
533
  clearTimeout(debounceTimer);
544
534
  }