@inshapardaz/likhari-react 0.1.25 → 0.1.26-dev.242

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.
@@ -7,7 +7,10 @@ import { type DraftRestoreMode } from './components/DraftRestore.js';
7
7
  import { type AutoCorrectStore } from './autocorrect/autoCorrectStores.js';
8
8
  import type { UrduNormalizationOptions } from './normalization/urduNormalize.js';
9
9
  import type { PunctuationOptions } from './autocorrect/punctuationRules.js';
10
+ import { type ThesaurusStore } from './thesaurus/thesaurusStores.js';
11
+ import { type CompletionStore } from './autocomplete/completionStores.js';
10
12
  import { type UserWordStore } from './spellcheck/userWords.js';
13
+ import type { SpellLanguage } from './spellcheck/spellDictionaries.js';
11
14
  import { type Locale } from './i18n/index.js';
12
15
  /** What happens when the user is about to leave with unsaved changes. */
13
16
  export type NavigationGuardMode = 'confirm' | 'save-draft' | 'off';
@@ -21,6 +24,12 @@ export interface EditorRootProps {
21
24
  autoCorrectStores?: AutoCorrectStore[];
22
25
  /** Where words added to the spelling dictionary are saved, in priority order. Pass a stable array. */
23
26
  dictionaryStores?: UserWordStore[];
27
+ /** Where autocomplete words come from, combined per language. Defaults to the dictionaries and the user's words. Pass a stable array. */
28
+ completionStores?: CompletionStore[];
29
+ /** Where synonyms come from, merged in order. Defaults to English WordNet. Pass a stable array. */
30
+ thesaurusStores?: ThesaurusStore[];
31
+ /** The language autocomplete works in: 'auto' follows each block's direction (English for LTR, Urdu for RTL). */
32
+ autoCompleteLanguage?: SpellLanguage | 'auto';
24
33
  /** Urdu normalisation applied as part of auto-correct. Diacritics are kept unless `removeDiacritics` is set. */
25
34
  urduNormalization?: UrduNormalizationOptions;
26
35
  /** Common punctuation fixes, and whether a straight " becomes ”. Both on by default. */
@@ -1 +1 @@
1
- {"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"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"}
@@ -32,6 +32,9 @@ import { AutoCorrectPlugin } from './autocorrect/AutoCorrectPlugin.js';
32
32
  import { localStorageAutoCorrectStore } from './autocorrect/autoCorrectStores.js';
33
33
  import { SpellcheckPanel } from './components/SpellcheckPanel.js';
34
34
  import { SpellHighlightPlugin } from './spellcheck/SpellHighlightPlugin.js';
35
+ import { AutocompletePlugin } from './autocomplete/AutocompletePlugin.js';
36
+ import { wordNetThesaurusStore } from './thesaurus/thesaurusStores.js';
37
+ import { dictionaryCompletionStore } from './autocomplete/completionStores.js';
35
38
  import { loadUserWords, localStorageUserWordStore } from './spellcheck/userWords.js';
36
39
  import { LeaveDialog } from './components/LeaveDialog.js';
37
40
  import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
@@ -43,6 +46,10 @@ const DEFAULT_AUTOSAVE_MAX_BYTES = 2000000;
43
46
  const DEFAULT_AUTOSAVE_MAX_DRAFTS = 20;
44
47
  /** Used when the host passes no stores: corrections are kept in this browser. */
45
48
  const DEFAULT_AUTOCORRECT_STORES = [localStorageAutoCorrectStore()];
49
+ /** Used when the host passes no thesaurus stores: English synonyms from WordNet, loaded on first use. */
50
+ const DEFAULT_THESAURUS_STORES = [wordNetThesaurusStore()];
51
+ /** Used when the host passes no completion stores: the dictionary's words and the user's added words. */
52
+ const DEFAULT_COMPLETION_STORES = [dictionaryCompletionStore()];
46
53
  /** Used when the host passes no dictionary stores: added words are kept in this browser. */
47
54
  const DEFAULT_DICTIONARY_STORES = [localStorageUserWordStore()];
48
55
  function initialEditorStateJson(initialContent) {
@@ -51,7 +58,7 @@ function initialEditorStateJson(initialContent) {
51
58
  const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
52
59
  return JSON.stringify(state);
53
60
  }
54
- export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorrectStores, dictionaryStores, urduNormalization, punctuation, initialContent, featureConfig, featurePreset, theme, colorScheme, accentColor, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, autosave = true, restoreDraft = 'prompt', navigationGuard = 'confirm', onDraftRestored, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, autosaveMaxBytes = DEFAULT_AUTOSAVE_MAX_BYTES, autosaveMaxDrafts = DEFAULT_AUTOSAVE_MAX_DRAFTS, }, ref) {
61
+ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorrectStores, dictionaryStores, completionStores, thesaurusStores, autoCompleteLanguage = 'auto', urduNormalization, punctuation, initialContent, featureConfig, featurePreset, theme, colorScheme, accentColor, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, autosave = true, restoreDraft = 'prompt', navigationGuard = 'confirm', onDraftRestored, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, autosaveMaxBytes = DEFAULT_AUTOSAVE_MAX_BYTES, autosaveMaxDrafts = DEFAULT_AUTOSAVE_MAX_DRAFTS, }, ref) {
55
62
  const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
56
63
  const strings = useMemo(() => getStrings(locale), [locale]);
57
64
  const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
@@ -87,6 +94,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorre
87
94
  const [autoCorrectVersion, setAutoCorrectVersion] = useState(0);
88
95
  const stores = autoCorrectStores ?? DEFAULT_AUTOCORRECT_STORES;
89
96
  const wordStores = dictionaryStores ?? DEFAULT_DICTIONARY_STORES;
97
+ const resolvedCompletionStores = completionStores ?? DEFAULT_COMPLETION_STORES;
90
98
  useEffect(() => {
91
99
  void loadUserWords(wordStores);
92
100
  }, [wordStores]);
@@ -303,9 +311,9 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorre
303
311
  // rather than colliding on a shared one.
304
312
  const portalTargetId = useId();
305
313
  const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
306
- return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, accentColor: accentColor, scopeElementId: portalTargetId, children: _jsxs("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: [_jsx("div", { id: portalTargetId, className: "likhari-portal-target" }), _jsx(PortalTargetContext.Provider, { value: portalTargetSelector, children: _jsxs(UiStringsContext.Provider, { value: strings, children: [_jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, dictionaryStores: wordStores, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onSave: handleSave, findOpen: openPanel === 'find', onToggleFind: () => togglePanel('find'), spellOpen: openPanel === 'spell', onToggleSpell: () => togglePanel('spell'), autoCorrectOpen: openPanel === 'autocorrect', onToggleAutoCorrect: () => togglePanel('autocorrect'), isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
314
+ return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, accentColor: accentColor, scopeElementId: portalTargetId, children: _jsxs("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: [_jsx("div", { id: portalTargetId, className: "likhari-portal-target" }), _jsx(PortalTargetContext.Provider, { value: portalTargetSelector, children: _jsxs(UiStringsContext.Provider, { value: strings, children: [_jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, dictionaryStores: wordStores, thesaurusStores: thesaurusStores ?? DEFAULT_THESAURUS_STORES, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onSave: handleSave, findOpen: openPanel === 'find', onToggleFind: () => togglePanel('find'), spellOpen: openPanel === 'spell', onToggleSpell: () => togglePanel('spell'), autoCorrectOpen: openPanel === 'autocorrect', onToggleAutoCorrect: () => togglePanel('autocorrect'), isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
307
315
  ? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
308
- : undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsxs("div", { className: "likhari-canvas-frame", children: [config.findReplace && openPanel === 'find' && _jsx(FindReplaceBar, { strings: strings, dir: dir, onClose: () => setOpenPanel(null) }), config.language.spellCheck && openPanel === 'spell' && (_jsx(SpellcheckPanel, { strings: strings, dir: dir, dictionaryStores: wordStores, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onClose: () => setOpenPanel(null) })), config.language.autocorrect && openPanel === 'autocorrect' && (_jsx(AutoCorrectPanel, { strings: strings, dir: dir, stores: stores, initialFrom: autoCorrectPrefill?.from, initialLanguage: autoCorrectPrefill?.language, onSaved: () => setAutoCorrectVersion((v) => v + 1), onClose: () => setOpenPanel(null) }, autoCorrectPrefill?.id ?? 0)), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, spellCheck: false, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) })] }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.pageBreak && _jsx(PageBreakPlugin, {}), config.language.spellCheck && _jsx(SpellHighlightPlugin, {}), (config.language.autocorrect || config.language.textCleanup) && _jsx(AutoCorrectPlugin, { stores: stores, version: autoCorrectVersion, enabled: true, typing: config.language.autocorrect, urduNormalization: urduNormalization, punctuation: punctuation }), config.columns && _jsx(LayoutPlugin, {}), config.footnotes && _jsx(FootnotePlugin, {}), config.poetry.enabled && _jsx(PoetryPlugin, {}), config.poetry.enabled && _jsx(PoetryResizer, {}), config.tables && _jsx(TablePlugin, { hasCellMerge: true, hasTabHandler: true }), config.blocks.horizontalRule && _jsx(HorizontalRulePlugin, {}), _jsx(EditorInstancePlugin, { instanceRef: editorInstanceRef }), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
316
+ : undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsxs("div", { className: "likhari-canvas-frame", children: [config.findReplace && openPanel === 'find' && _jsx(FindReplaceBar, { strings: strings, dir: dir, onClose: () => setOpenPanel(null) }), config.language.spellCheck && openPanel === 'spell' && (_jsx(SpellcheckPanel, { strings: strings, dir: dir, dictionaryStores: wordStores, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onClose: () => setOpenPanel(null) })), config.language.autocorrect && openPanel === 'autocorrect' && (_jsx(AutoCorrectPanel, { strings: strings, dir: dir, stores: stores, initialFrom: autoCorrectPrefill?.from, initialLanguage: autoCorrectPrefill?.language, onSaved: () => setAutoCorrectVersion((v) => v + 1), onClose: () => setOpenPanel(null) }, autoCorrectPrefill?.id ?? 0)), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, spellCheck: false, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) })] }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.pageBreak && _jsx(PageBreakPlugin, {}), config.language.spellCheck && _jsx(SpellHighlightPlugin, {}), config.language.autocomplete && (_jsx(AutocompletePlugin, { label: strings.autoComplete.label, stores: resolvedCompletionStores, language: autoCompleteLanguage })), (config.language.autocorrect || config.language.textCleanup) && _jsx(AutoCorrectPlugin, { stores: stores, version: autoCorrectVersion, enabled: true, typing: config.language.autocorrect, urduNormalization: urduNormalization, punctuation: punctuation }), config.columns && _jsx(LayoutPlugin, {}), config.footnotes && _jsx(FootnotePlugin, {}), config.poetry.enabled && _jsx(PoetryPlugin, {}), config.poetry.enabled && _jsx(PoetryResizer, {}), config.tables && _jsx(TablePlugin, { hasCellMerge: true, hasTabHandler: true }), config.blocks.horizontalRule && _jsx(HorizontalRulePlugin, {}), _jsx(EditorInstancePlugin, { instanceRef: editorInstanceRef }), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
309
317
  handleSave();
310
318
  settleLeave(true);
311
319
  }, onSaveDraft: () => {
@@ -0,0 +1,16 @@
1
+ import type { SpellLanguage } from '../spellcheck/spellDictionaries.js';
2
+ import type { CompletionStore } from './completionStores.js';
3
+ /**
4
+ * Suggests completions for the word being typed, from the dictionary's words and
5
+ * the user's own. Shown only while typing, at the caret. Tab or Enter accepts the
6
+ * highlighted suggestion, the arrow keys move through the list, and Escape (or
7
+ * moving the caret) dismisses it. Left-to-right text uses the English dictionary,
8
+ * right-to-left text the Urdu one.
9
+ */
10
+ export declare function AutocompletePlugin({ label, stores, language: chosenLanguage, }: {
11
+ label: string;
12
+ stores: CompletionStore[];
13
+ /** The language to complete in, or 'auto' to follow the block's direction. */
14
+ language: SpellLanguage | 'auto';
15
+ }): import("react").JSX.Element | null;
16
+ //# sourceMappingURL=AutocompletePlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AutocompletePlugin.d.ts","sourceRoot":"","sources":["../../src/autocomplete/AutocompletePlugin.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAyB1D;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,MAAM,EACN,QAAQ,EAAE,cAAc,GACzB,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,EAAE,aAAa,GAAG,MAAM,CAAC;CAClC,sCAuMA"}
@@ -0,0 +1,285 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
3
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
+ import { $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ENTER_COMMAND, KEY_ESCAPE_COMMAND, KEY_TAB_COMMAND, } from 'lexical';
5
+ import { $replaceMatch } from '../find/findReplaceActions.js';
6
+ import { onSpellWordsChange } from '../spellcheck/userWords.js';
7
+ import { WordIndex } from './wordIndex.js';
8
+ /** The letters at the end of the text before the caret. */
9
+ const WORD_END = /[\p{L}\p{M}]+$/u;
10
+ const LETTER = /[\p{L}\p{M}]/u;
11
+ const MOVEMENT_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown']);
12
+ /** Suggestions only appear once the typed part of a word is this long. */
13
+ const MIN_PREFIX = 2;
14
+ /**
15
+ * Suggests completions for the word being typed, from the dictionary's words and
16
+ * the user's own. Shown only while typing, at the caret. Tab or Enter accepts the
17
+ * highlighted suggestion, the arrow keys move through the list, and Escape (or
18
+ * moving the caret) dismisses it. Left-to-right text uses the English dictionary,
19
+ * right-to-left text the Urdu one.
20
+ */
21
+ export function AutocompletePlugin({ label, stores, language: chosenLanguage, }) {
22
+ const [editor] = useLexicalComposerContext();
23
+ // Effects run once per editor, so they read the latest props through refs.
24
+ const storesRef = useRef(stores);
25
+ storesRef.current = stores;
26
+ const languageRef = useRef(chosenLanguage);
27
+ languageRef.current = chosenLanguage;
28
+ const [popup, setPopup] = useState(null);
29
+ const popupRef = useRef(null);
30
+ const typing = useRef(false);
31
+ const indexes = useRef(new Map());
32
+ const show = (next) => {
33
+ popupRef.current = next;
34
+ setPopup(next);
35
+ };
36
+ /** Counts an accepted completion in every store that keeps counts, then drops the cached lists. */
37
+ const recordAcceptance = (language, word) => {
38
+ for (const store of storesRef.current) {
39
+ store.recordAccept?.(language, word).catch(() => undefined);
40
+ }
41
+ indexes.current.clear();
42
+ };
43
+ useEffect(() => {
44
+ const indexFor = (language) => {
45
+ let index = indexes.current.get(language);
46
+ if (!index) {
47
+ index = Promise.all(storesRef.current.map(async (store) => {
48
+ try {
49
+ const [words, counts] = await Promise.all([
50
+ store.load(language),
51
+ store.loadCounts ? store.loadCounts(language) : Promise.resolve({}),
52
+ ]);
53
+ return { words, counts };
54
+ }
55
+ catch {
56
+ return { words: [], counts: {} };
57
+ }
58
+ })).then((parts) => {
59
+ const counts = {};
60
+ for (const part of parts)
61
+ for (const [word, count] of Object.entries(part.counts))
62
+ counts[word] = (counts[word] ?? 0) + count;
63
+ return new WordIndex(parts.flatMap((part) => part.words), counts);
64
+ });
65
+ indexes.current.set(language, index);
66
+ }
67
+ return index;
68
+ };
69
+ const hide = () => show(null);
70
+ const refresh = () => {
71
+ if (!typing.current)
72
+ return hide();
73
+ let target = null;
74
+ editor.getEditorState().read(() => {
75
+ const selection = $getSelection();
76
+ if (!$isRangeSelection(selection) || !selection.isCollapsed())
77
+ return;
78
+ const node = selection.anchor.getNode();
79
+ if (!$isTextNode(node))
80
+ return;
81
+ const text = node.getTextContent();
82
+ const before = text.slice(0, selection.anchor.offset);
83
+ const prefix = WORD_END.exec(before)?.[0];
84
+ const after = text.charAt(selection.anchor.offset);
85
+ if (!prefix || prefix.length < MIN_PREFIX || (after !== '' && LETTER.test(after)))
86
+ return;
87
+ target = { prefix, rtl: node.getParentOrThrow().getDirection() === 'rtl' };
88
+ });
89
+ if (target === null)
90
+ return hide();
91
+ const { prefix, rtl } = target;
92
+ const language = languageRef.current === 'auto' ? (rtl ? 'ur' : 'en') : languageRef.current;
93
+ void indexFor(language).then((loaded) => {
94
+ // Same word as the open list: keep the user's highlight rather than resetting it.
95
+ const open = popupRef.current;
96
+ if (open && open.prefix === prefix && open.dir === (rtl ? 'rtl' : 'ltr'))
97
+ return;
98
+ const items = loaded.complete(prefix);
99
+ if (items.length === 0)
100
+ return hide();
101
+ const rect = caretRect(editor);
102
+ show({ prefix, language, items, index: 0, selected: false, ...rect, dir: rtl ? 'rtl' : 'ltr' });
103
+ });
104
+ };
105
+ // Handles an arrow key for the list, and cancels the browser's own caret movement.
106
+ const move = (delta, event) => {
107
+ const current = popupRef.current;
108
+ // Still loading the list for the word being typed: the arrow is for the list, not the caret.
109
+ if (!current && prefixAtCaret(editor) === null)
110
+ return false;
111
+ event?.preventDefault();
112
+ if (current) {
113
+ const count = current.items.length;
114
+ show({ ...current, index: (current.index + delta + count) % count, selected: true });
115
+ }
116
+ return true;
117
+ };
118
+ const onKeyDown = (event) => {
119
+ // Up, Down, Tab and Enter act on the list through their editor commands, which
120
+ // run after this listener, so the list must stay open for them.
121
+ const current = popupRef.current;
122
+ if (current && (event.key === 'Tab' || (event.key === 'Enter' && current.selected)))
123
+ return;
124
+ if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') && (current || prefixAtCaret(editor) !== null))
125
+ return;
126
+ // Left, Right, Home and End move the caret away from the word, so the list closes.
127
+ if (MOVEMENT_KEYS.has(event.key)) {
128
+ typing.current = false;
129
+ return hide();
130
+ }
131
+ // Printable keys and Backspace are typing; anything else (Enter, Tab, ...) ends the word.
132
+ typing.current = (event.key.length === 1 && !event.ctrlKey && !event.metaKey) || event.key === 'Backspace';
133
+ if (!typing.current)
134
+ hide();
135
+ setTimeout(refresh, 0);
136
+ };
137
+ // Text that arrives without a keydown (input methods, some keyboard layouts) is typing too.
138
+ const onInput = () => {
139
+ typing.current = true;
140
+ setTimeout(refresh, 0);
141
+ };
142
+ const onMouseDown = () => {
143
+ typing.current = false;
144
+ hide();
145
+ };
146
+ let root = null;
147
+ const attach = (next) => {
148
+ root?.removeEventListener('keydown', onKeyDown);
149
+ root?.removeEventListener('mousedown', onMouseDown);
150
+ root?.removeEventListener('input', onInput);
151
+ root = next;
152
+ root?.addEventListener('keydown', onKeyDown);
153
+ root?.addEventListener('mousedown', onMouseDown);
154
+ root?.addEventListener('input', onInput);
155
+ };
156
+ const unregisterRoot = editor.registerRootListener((next) => attach(next));
157
+ const unregisterUpdate = editor.registerUpdateListener(() => {
158
+ // Keydown already schedules a refresh; this keeps the list right after the
159
+ // dictionary or user words change.
160
+ if (typing.current)
161
+ setTimeout(refresh, 0);
162
+ });
163
+ const unregisterWords = onSpellWordsChange(() => {
164
+ indexes.current.clear();
165
+ if (typing.current)
166
+ setTimeout(refresh, 0);
167
+ });
168
+ const unregisterCommands = [
169
+ editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (event) => move(1, event), COMMAND_PRIORITY_HIGH),
170
+ editor.registerCommand(KEY_ARROW_UP_COMMAND, (event) => move(-1, event), COMMAND_PRIORITY_HIGH),
171
+ // While the list is open, Escape only closes it. Lexical would otherwise blur the editor.
172
+ editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
173
+ if (!popupRef.current)
174
+ return false;
175
+ show(null);
176
+ return true;
177
+ }, COMMAND_PRIORITY_HIGH),
178
+ editor.registerCommand(KEY_TAB_COMMAND, (event) => acceptHighlighted(event), COMMAND_PRIORITY_HIGH),
179
+ editor.registerCommand(KEY_ENTER_COMMAND, (event) => (popupRef.current?.selected ? acceptHighlighted(event) : false), COMMAND_PRIORITY_HIGH),
180
+ ];
181
+ function acceptHighlighted(event) {
182
+ const current = popupRef.current;
183
+ if (!current)
184
+ return false;
185
+ event?.preventDefault();
186
+ const word = current.items[current.index];
187
+ acceptItem(editor, word);
188
+ recordAcceptance(current.language, word);
189
+ show(null);
190
+ return true;
191
+ }
192
+ return () => {
193
+ unregisterRoot();
194
+ unregisterUpdate();
195
+ unregisterWords();
196
+ unregisterCommands.forEach((unregister) => unregister());
197
+ attach(null);
198
+ show(null);
199
+ };
200
+ }, [editor]);
201
+ // The index is per store set and language; a change of stores starts again.
202
+ useEffect(() => {
203
+ indexes.current.clear();
204
+ }, [stores]);
205
+ if (!popup)
206
+ return null;
207
+ return _jsx(SuggestionList, { popup: popup, label: label, onPick: (item) => {
208
+ acceptItem(editor, item);
209
+ recordAcceptance(popup.language, item);
210
+ show(null);
211
+ } });
212
+ }
213
+ function SuggestionList({ popup, label, onPick }) {
214
+ const ref = useRef(null);
215
+ // Placed after it is drawn, so its real size is known: kept inside the window, on the side of the
216
+ // caret that has room, and right-to-left lists align their right edge with the caret.
217
+ useLayoutEffect(() => {
218
+ const element = ref.current;
219
+ if (!element)
220
+ return;
221
+ const margin = 8;
222
+ const width = element.offsetWidth;
223
+ const height = element.offsetHeight;
224
+ const preferredLeft = popup.dir === 'rtl' ? popup.right - width : popup.left;
225
+ const left = Math.min(Math.max(preferredLeft, margin), window.innerWidth - width - margin);
226
+ const below = popup.bottom + 4;
227
+ const fitsBelow = below + height <= window.innerHeight - margin;
228
+ const top = fitsBelow ? below : Math.max(popup.top - height - 4, margin);
229
+ element.style.left = `${Math.max(left, margin)}px`;
230
+ element.style.top = `${top}px`;
231
+ });
232
+ return (_jsx("div", { ref: ref, className: "likhari-autocomplete", role: "listbox", "aria-label": label, dir: popup.dir, style: { position: 'fixed' }, children: popup.items.map((item, i) => (_jsx("div", { role: "option", "aria-selected": i === popup.index, className: i === popup.index ? 'likhari-autocomplete-item likhari-autocomplete-item--active' : 'likhari-autocomplete-item', onMouseDown: (event) => {
233
+ event.preventDefault();
234
+ onPick(item);
235
+ }, children: item }, item))) }));
236
+ }
237
+ /** The typed part of the word at the caret, when the caret is at its end and the word is long enough to complete. */
238
+ function prefixAtCaret(editor) {
239
+ let prefix = null;
240
+ editor.getEditorState().read(() => {
241
+ const selection = $getSelection();
242
+ if (!$isRangeSelection(selection) || !selection.isCollapsed())
243
+ return;
244
+ const node = selection.anchor.getNode();
245
+ if (!$isTextNode(node))
246
+ return;
247
+ const text = node.getTextContent();
248
+ const before = text.slice(0, selection.anchor.offset);
249
+ const word = WORD_END.exec(before)?.[0];
250
+ const after = text.charAt(selection.anchor.offset);
251
+ if (word && word.length >= MIN_PREFIX && !(after !== '' && LETTER.test(after)))
252
+ prefix = word;
253
+ });
254
+ return prefix;
255
+ }
256
+ /** Where to put the popup: under the caret, or the editor's top-left if the caret has no position yet. */
257
+ function caretRect(editor) {
258
+ const selection = window.getSelection();
259
+ if (selection && selection.rangeCount > 0) {
260
+ const rect = selection.getRangeAt(0).getBoundingClientRect();
261
+ if (rect.width > 0 || rect.height > 0)
262
+ return { left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom };
263
+ }
264
+ const root = editor.getRootElement()?.getBoundingClientRect();
265
+ const left = root?.left ?? 0;
266
+ const top = root?.top ?? 0;
267
+ return { left, right: left, top, bottom: top };
268
+ }
269
+ function acceptItem(editor, item) {
270
+ editor.update(() => {
271
+ const selection = $getSelection();
272
+ if (!$isRangeSelection(selection) || !selection.isCollapsed())
273
+ return;
274
+ const node = selection.anchor.getNode();
275
+ if (!$isTextNode(node))
276
+ return;
277
+ const before = node.getTextContent().slice(0, selection.anchor.offset);
278
+ const prefix = WORD_END.exec(before)?.[0];
279
+ if (!prefix)
280
+ return;
281
+ const start = before.length - prefix.length;
282
+ $replaceMatch({ anchorKey: node.getKey(), anchorOffset: start, focusKey: node.getKey(), focusOffset: before.length }, item);
283
+ }, { discrete: true });
284
+ editor.focus();
285
+ }
@@ -0,0 +1,34 @@
1
+ import { type SpellLanguage } from '../spellcheck/spellDictionaries.js';
2
+ /**
3
+ * Where autocomplete words come from, and how often each was accepted. The host
4
+ * can pass several; words are combined per language and their counts summed.
5
+ * A failing store is skipped.
6
+ */
7
+ export interface CompletionStore {
8
+ readonly id: string;
9
+ load(language: SpellLanguage): Promise<string[]>;
10
+ /** How many times each word was accepted as a completion. */
11
+ loadCounts?(language: SpellLanguage): Promise<Record<string, number>>;
12
+ /** Records one accepted completion, so the word is suggested sooner next time. */
13
+ recordAccept?(language: SpellLanguage, word: string): Promise<void>;
14
+ }
15
+ /**
16
+ * The default: the dictionary's words plus the words the user has added. Accepted
17
+ * completions are counted in this browser's localStorage, and the most accepted
18
+ * words are suggested first.
19
+ */
20
+ export declare function dictionaryCompletionStore(options?: {
21
+ countsKey?: string;
22
+ }): CompletionStore;
23
+ /**
24
+ * Words and counts served by an API. `load` calls `GET {url}?language=xx` for a JSON
25
+ * array of strings; `loadCounts` calls `GET {url}/counts?language=xx` for a JSON
26
+ * object of counts; `recordAccept` calls `POST {url}/accept` with `{ language, word }`.
27
+ */
28
+ export declare function apiCompletionStore(options: {
29
+ id?: string;
30
+ url: string;
31
+ headers?: Record<string, string>;
32
+ fetch?: typeof fetch;
33
+ }): CompletionStore;
34
+ //# sourceMappingURL=completionStores.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completionStores.d.ts","sourceRoot":"","sources":["../../src/autocomplete/completionStores.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGzF;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjD,6DAA6D;IAC7D,UAAU,CAAC,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,kFAAkF;IAClF,YAAY,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAiBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,eAAe,CAwB/F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB,GAAG,eAAe,CA0BlB"}
@@ -0,0 +1,78 @@
1
+ import { getDictionaryWords } from '../spellcheck/spellDictionaries.js';
2
+ import { acceptedWords } from '../spellcheck/userWords.js';
3
+ const COUNTS_KEY = 'likhari-completion-counts';
4
+ function readCounts(key) {
5
+ try {
6
+ const parsed = JSON.parse(localStorage.getItem(key) ?? '{}');
7
+ return parsed && typeof parsed === 'object' ? parsed : {};
8
+ }
9
+ catch {
10
+ return {};
11
+ }
12
+ }
13
+ /**
14
+ * The default: the dictionary's words plus the words the user has added. Accepted
15
+ * completions are counted in this browser's localStorage, and the most accepted
16
+ * words are suggested first.
17
+ */
18
+ export function dictionaryCompletionStore(options = {}) {
19
+ const key = options.countsKey ?? COUNTS_KEY;
20
+ return {
21
+ id: 'dictionary',
22
+ async load(language) {
23
+ const words = getDictionaryWords(language);
24
+ const dictionary = words ? await words : [];
25
+ return [...dictionary, ...acceptedWords(language)];
26
+ },
27
+ async loadCounts(language) {
28
+ return readCounts(key)[language] ?? {};
29
+ },
30
+ async recordAccept(language, word) {
31
+ const all = readCounts(key);
32
+ const counts = all[language] ?? {};
33
+ counts[word] = (counts[word] ?? 0) + 1;
34
+ all[language] = counts;
35
+ try {
36
+ localStorage.setItem(key, JSON.stringify(all));
37
+ }
38
+ catch {
39
+ // Storage full or disabled: the count is kept for this session only.
40
+ }
41
+ },
42
+ };
43
+ }
44
+ /**
45
+ * Words and counts served by an API. `load` calls `GET {url}?language=xx` for a JSON
46
+ * array of strings; `loadCounts` calls `GET {url}/counts?language=xx` for a JSON
47
+ * object of counts; `recordAccept` calls `POST {url}/accept` with `{ language, word }`.
48
+ */
49
+ export function apiCompletionStore(options) {
50
+ const doFetch = options.fetch ?? fetch;
51
+ const query = (language) => `?language=${encodeURIComponent(language)}`;
52
+ return {
53
+ id: options.id ?? 'api',
54
+ async load(language) {
55
+ const response = await doFetch(`${options.url}${query(language)}`, { headers: options.headers });
56
+ if (!response.ok)
57
+ throw new Error(`Completion API failed: ${response.status}`);
58
+ const data = await response.json();
59
+ return Array.isArray(data) ? data.filter((w) => typeof w === 'string') : [];
60
+ },
61
+ async loadCounts(language) {
62
+ const response = await doFetch(`${options.url}/counts${query(language)}`, { headers: options.headers });
63
+ if (!response.ok)
64
+ throw new Error(`Completion counts failed: ${response.status}`);
65
+ const data = await response.json();
66
+ return data && typeof data === 'object' ? data : {};
67
+ },
68
+ async recordAccept(language, word) {
69
+ const response = await doFetch(`${options.url}/accept`, {
70
+ method: 'POST',
71
+ headers: { 'Content-Type': 'application/json', ...options.headers },
72
+ body: JSON.stringify({ language, word }),
73
+ });
74
+ if (!response.ok)
75
+ throw new Error(`Completion API rejected the count: ${response.status}`);
76
+ },
77
+ };
78
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Completion words, sorted once so every prefix lookup is a binary search. Each
3
+ * word has a count of how often it was accepted as a completion; more used words
4
+ * are suggested first.
5
+ */
6
+ export declare class WordIndex {
7
+ private readonly words;
8
+ private readonly counts;
9
+ constructor(words: Iterable<string>, counts?: Record<string, number>);
10
+ /**
11
+ * Completions for a prefix: words that start with it (other than the prefix
12
+ * itself), most accepted first, then shortest, then alphabetical. Only the first
13
+ * `scanLimit` matches of the sorted words are scanned, so a one-letter prefix stays
14
+ * quick; words with a count are always considered.
15
+ */
16
+ complete(prefix: string, limit?: number, scanLimit?: number): string[];
17
+ private countOf;
18
+ private lowerBound;
19
+ }
20
+ //# sourceMappingURL=wordIndex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wordIndex.d.ts","sourceRoot":"","sources":["../../src/autocomplete/wordIndex.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAEjC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAKxE;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAI,EAAE,SAAS,SAAM,GAAG,MAAM,EAAE;IAgB9D,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,UAAU;CAUnB"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Completion words, sorted once so every prefix lookup is a binary search. Each
3
+ * word has a count of how often it was accepted as a completion; more used words
4
+ * are suggested first.
5
+ */
6
+ export class WordIndex {
7
+ constructor(words, counts = {}) {
8
+ this.counts = new Map(Object.entries(counts));
9
+ this.words = [...new Set([...words, ...this.counts.keys()])].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
10
+ }
11
+ /**
12
+ * Completions for a prefix: words that start with it (other than the prefix
13
+ * itself), most accepted first, then shortest, then alphabetical. Only the first
14
+ * `scanLimit` matches of the sorted words are scanned, so a one-letter prefix stays
15
+ * quick; words with a count are always considered.
16
+ */
17
+ complete(prefix, limit = 5, scanLimit = 500) {
18
+ if (prefix === '')
19
+ return [];
20
+ const candidates = new Set();
21
+ for (let i = this.lowerBound(prefix), scanned = 0; i < this.words.length && scanned < scanLimit; i++) {
22
+ const word = this.words[i];
23
+ if (!word.startsWith(prefix))
24
+ break;
25
+ if (word !== prefix)
26
+ candidates.add(word);
27
+ scanned += 1;
28
+ }
29
+ for (const word of this.counts.keys())
30
+ if (word !== prefix && word.startsWith(prefix))
31
+ candidates.add(word);
32
+ return [...candidates]
33
+ .sort((a, b) => (this.countOf(b) - this.countOf(a)) || a.length - b.length || (a < b ? -1 : a > b ? 1 : 0))
34
+ .slice(0, limit);
35
+ }
36
+ countOf(word) {
37
+ return this.counts.get(word) ?? 0;
38
+ }
39
+ lowerBound(prefix) {
40
+ let low = 0;
41
+ let high = this.words.length;
42
+ while (low < high) {
43
+ const mid = (low + high) >>> 1;
44
+ if (this.words[mid] < prefix)
45
+ low = mid + 1;
46
+ else
47
+ high = mid;
48
+ }
49
+ return low;
50
+ }
51
+ }
@@ -1,4 +1,5 @@
1
1
  import { type SpellLanguage } from '../spellcheck/spellDictionaries.js';
2
+ import { type ThesaurusStore } from '../thesaurus/thesaurusStores.js';
2
3
  import { type UserWordStore } from '../spellcheck/userWords.js';
3
4
  import { type DraftsToolbarOptions } from './DraftsDialog.js';
4
5
  import type { ResolvedEditorFeatureConfig } from '@inshapardaz/likhari-core';
@@ -6,6 +7,8 @@ import { type FontOption } from '../fonts/index.js';
6
7
  import type { Locale } from '../i18n/strings.js';
7
8
  export interface ToolbarProps {
8
9
  config: ResolvedEditorFeatureConfig;
10
+ /** Where synonyms come from. */
11
+ thesaurusStores?: ThesaurusStore[];
9
12
  /** Where words added to the dictionary are saved. */
10
13
  dictionaryStores?: UserWordStore[];
11
14
  /** Opens the auto-correct panel with the word filled in. */
@@ -31,5 +34,5 @@ export interface ToolbarProps {
31
34
  /** Autosave drafts: when set, the toolbar offers a Drafts button that lists and restores them. */
32
35
  drafts?: DraftsToolbarOptions;
33
36
  }
34
- export declare function Toolbar({ config, dictionaryStores, onAddAutoCorrect, onSave, isDirty, showSave, findOpen, onToggleFind, spellOpen, onToggleSpell, autoCorrectOpen, onToggleAutoCorrect, fontOptions, direction, locale, drafts }: ToolbarProps): import("react").JSX.Element;
37
+ export declare function Toolbar({ config, dictionaryStores, thesaurusStores, onAddAutoCorrect, onSave, isDirty, showSave, findOpen, onToggleFind, spellOpen, onToggleSpell, autoCorrectOpen, onToggleAutoCorrect, fontOptions, direction, locale, drafts }: ToolbarProps): import("react").JSX.Element;
35
38
  //# sourceMappingURL=Toolbar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA0CA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAgDtF,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AA6hBvD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;IACnC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IACnE,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,wFAAwF;IACxF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,iGAAiG;IACjG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,gBAAqB,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAgB,EAAE,YAAY,EAAE,SAAiB,EAAE,aAAa,EAAE,eAAuB,EAAE,mBAAmB,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAqnCjT"}
1
+ {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA0CA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAgDtF,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AA+hBvD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,gCAAgC;IAChC,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;IACnC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IACnE,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,wFAAwF;IACxF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,iGAAiG;IACjG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,gBAAqB,EAAE,eAAoB,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAgB,EAAE,YAAY,EAAE,SAAiB,EAAE,aAAa,EAAE,eAAuB,EAAE,mBAAmB,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAspCvU"}
@@ -11,6 +11,7 @@ import { $findMatchingParent, $insertNodeToNearestRoot } from '@lexical/utils';
11
11
  import { CORRECT_DOCUMENT_COMMAND } from '../autocorrect/AutoCorrectPlugin.js';
12
12
  import { $replaceMatch } from '../find/findReplaceActions.js';
13
13
  import { getSpeller } from '../spellcheck/spellDictionaries.js';
14
+ import { lookupSynonyms } from '../thesaurus/thesaurusStores.js';
14
15
  import { addUserWord, ignoreWord } from '../spellcheck/userWords.js';
15
16
  import { $wordAtDomPoint, textPointAt } from '../spellcheck/spellingMenu.js';
16
17
  import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode';
@@ -185,7 +186,7 @@ function PoetryMenuItems({ strings, layout, scale, centered, onAction, }) {
185
186
  };
186
187
  return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconColumns1, t.singleColumn, setLayout('single'), layout === 'single'), item(IconColumns2, t.twoColumn, setLayout('two-column'), layout === 'two-column'), item(IconArrowsShuffle, t.staggered, setLayout('staggered'), layout === 'staggered'), _jsx(Menu.Divider, {}), _jsx(Menu.Divider, {}), item(IconRowInsertTop, t.insertCoupletBefore, () => $insertCoupletRelativeToSelection('before')), item(IconRowInsertBottom, t.insertCoupletAfter, () => $insertCoupletRelativeToSelection('after')), item(IconArrowsMinimize, t.tighterSpacing, () => $adjustPoetrySpacing(-1), !canStep(POETRY_SPACINGS, scale.spacing, -1)), item(IconArrowsMaximize, t.looserSpacing, () => $adjustPoetrySpacing(1), !canStep(POETRY_SPACINGS, scale.spacing, 1)), layout === 'staggered' && (_jsxs(_Fragment, { children: [item(IconArrowsMinimize, t.narrowerCouplets, () => $adjustPoetryStagger(-1), !canStep(POETRY_STAGGERS, scale.stagger, -1)), item(IconArrowsMaximize, t.widerCouplets, () => $adjustPoetryStagger(1), !canStep(POETRY_STAGGERS, scale.stagger, 1))] })), layout === 'two-column' && (_jsxs(_Fragment, { children: [item(IconAlignCenter, centered ? t.uncenterCouplet : t.centerCouplet, () => $setCoupletCentered(!centered)), item(IconColumns2, t.narrowerGutter, () => $adjustPoetryGutter(-1), !canStep(POETRY_GUTTERS, scale.gutter, -1)), item(IconColumns2, t.widerGutter, () => $adjustPoetryGutter(1), !canStep(POETRY_GUTTERS, scale.gutter, 1))] })), item(IconTrash, t.deleteCouplet, $deletePoetryCouplet, false, 'red'), item(IconTrash, t.deletePoetry, $deletePoetryBlock, false, 'red')] }));
187
188
  }
188
- export function Toolbar({ config, dictionaryStores = [], onAddAutoCorrect, onSave, isDirty, showSave, findOpen = false, onToggleFind, spellOpen = false, onToggleSpell, autoCorrectOpen = false, onToggleAutoCorrect, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
189
+ export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], onAddAutoCorrect, onSave, isDirty, showSave, findOpen = false, onToggleFind, spellOpen = false, onToggleSpell, autoCorrectOpen = false, onToggleAutoCorrect, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
189
190
  const [editor] = useLexicalComposerContext();
190
191
  const [state, setState] = useState(INITIAL_STATE);
191
192
  const strings = useStrings(locale);
@@ -483,7 +484,7 @@ export function Toolbar({ config, dictionaryStores = [], onAddAutoCorrect, onSav
483
484
  const anchor = config.links ? (target?.closest?.('a') ?? null) : null;
484
485
  const cell = config.tables ? (target?.closest?.('td, th') ?? null) : null;
485
486
  const couplet = config.poetry.enabled ? (target?.closest?.('.likhari-poetry') ?? null) : null;
486
- const point = config.language.spellCheck ? textPointAt(event.clientX, event.clientY) : null;
487
+ const point = config.language.spellCheck || config.language.thesaurus ? textPointAt(event.clientX, event.clientY) : null;
487
488
  const menu = { x: event.clientX, y: event.clientY };
488
489
  let word = null;
489
490
  editor.update(() => {
@@ -521,8 +522,11 @@ export function Toolbar({ config, dictionaryStores = [], onAddAutoCorrect, onSav
521
522
  if (point)
522
523
  word = $wordAtDomPoint(point.node, point.offset);
523
524
  }, { discrete: true });
524
- if (word) {
525
- const language = word.direction === 'rtl' ? 'ur' : 'en';
525
+ const language = word?.direction === 'rtl' ? 'ur' : 'en';
526
+ if (word && config.language.thesaurus) {
527
+ menu.thesaurus = { language, word: word.word, match: word.match };
528
+ }
529
+ if (word && config.language.spellCheck) {
526
530
  const speller = getSpeller(language);
527
531
  if (speller) {
528
532
  const checked = await speller;
@@ -532,12 +536,20 @@ export function Toolbar({ config, dictionaryStores = [], onAddAutoCorrect, onSav
532
536
  }
533
537
  }
534
538
  setContextMenu(menu);
539
+ if (word && menu.thesaurus) {
540
+ // Synonyms can take a while (the first WordNet lookup downloads the database), so the menu
541
+ // is already open and fills in when they arrive, unless the user has moved on.
542
+ const items = await lookupSynonyms(thesaurusStores, word.word, language);
543
+ setContextMenu((current) => current && current.x === menu.x && current.y === menu.y && current.thesaurus
544
+ ? { ...current, thesaurus: { ...current.thesaurus, items } }
545
+ : current);
546
+ }
535
547
  };
536
548
  return editor.registerRootListener((root, previous) => {
537
549
  previous?.removeEventListener('contextmenu', onContextMenu);
538
550
  root?.addEventListener('contextmenu', onContextMenu);
539
551
  });
540
- }, [editor, config.links, config.tables, config.poetry.enabled, config.language.spellCheck]);
552
+ }, [editor, config.links, config.tables, config.poetry.enabled, config.language.spellCheck, config.language.thesaurus, thesaurusStores]);
541
553
  // Cut and copy put the selected text on the clipboard as plain text.
542
554
  const copyOrCut = (cut) => async () => {
543
555
  restoreSelection();
@@ -845,7 +857,11 @@ export function Toolbar({ config, dictionaryStores = [], onAddAutoCorrect, onSav
845
857
  }, children: strings.contextMenu.addToDictionary }), onAddAutoCorrect && config.language.autocorrect && (_jsx(Menu.Item, { onClick: () => {
846
858
  onAddAutoCorrect(contextMenu.spelling.word, contextMenu.spelling.language);
847
859
  setContextMenu(null);
848
- }, children: strings.contextMenu.addToAutoCorrect })), _jsx(Menu.Divider, {})] })), contextMenu?.link !== undefined && (_jsxs(_Fragment, { children: [_jsx(LinkMenuItems, { url: contextMenu.link, onEdit: () => {
860
+ }, children: strings.contextMenu.addToAutoCorrect })), _jsx(Menu.Divider, {})] })), contextMenu?.thesaurus && (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: strings.thesaurus.synonyms }), contextMenu.thesaurus.items === undefined && _jsx(Menu.Item, { disabled: true, children: strings.thesaurus.loading }), contextMenu.thesaurus.items?.length === 0 && _jsx(Menu.Item, { disabled: true, children: strings.thesaurus.none }), contextMenu.thesaurus.items?.map((synonym) => (_jsx(Menu.Item, { onClick: () => {
861
+ const match = contextMenu.thesaurus.match;
862
+ editor.update(() => $replaceMatch(match, synonym), { discrete: true });
863
+ editor.focus();
864
+ }, children: synonym }, synonym))), _jsx(Menu.Divider, {})] })), contextMenu?.link !== undefined && (_jsxs(_Fragment, { children: [_jsx(LinkMenuItems, { url: contextMenu.link, onEdit: () => {
849
865
  restoreSelection();
850
866
  openLinkDialog();
851
867
  }, onRemove: removeLink, strings: strings }), _jsx(Menu.Divider, {})] })), contextMenu?.table && (_jsxs(_Fragment, { children: [_jsx(TableMenuItems, { strings: strings, direction: direction, rows: contextMenu.table.rows, columns: contextMenu.table.columns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }), _jsx(Menu.Divider, {})] })), contextMenu?.poetry && (_jsxs(_Fragment, { children: [_jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }), _jsx(Menu.Divider, {})] })), _jsx(Menu.Item, { leftSection: _jsx(IconCut, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void copyOrCut(true)(), children: strings.contextMenu.cut }), _jsx(Menu.Item, { leftSection: _jsx(IconCopy, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void copyOrCut(false)(), children: strings.contextMenu.copy }), _jsx(Menu.Item, { leftSection: _jsx(IconClipboard, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void paste(), children: strings.contextMenu.paste }), _jsx(Menu.Item, { leftSection: _jsx(IconSelectAll, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: runMenuAction(() => $selectAll()), children: strings.contextMenu.selectAll })] })] }), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
@@ -204,6 +204,14 @@ export interface Strings {
204
204
  spelling: string;
205
205
  noSuggestions: string;
206
206
  };
207
+ autoComplete: {
208
+ label: string;
209
+ };
210
+ thesaurus: {
211
+ synonyms: string;
212
+ loading: string;
213
+ none: string;
214
+ };
207
215
  link: {
208
216
  menuLabel: string;
209
217
  noUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
@@ -187,6 +187,14 @@ const en = {
187
187
  spelling: 'Spelling suggestions',
188
188
  noSuggestions: 'No suggestions',
189
189
  },
190
+ autoComplete: {
191
+ label: 'Suggestions',
192
+ },
193
+ thesaurus: {
194
+ synonyms: 'Synonyms',
195
+ loading: 'Loading synonyms…',
196
+ none: 'No synonyms',
197
+ },
190
198
  link: {
191
199
  menuLabel: 'Link',
192
200
  noUrl: '(no URL)',
@@ -455,6 +463,14 @@ const ur = {
455
463
  spelling: 'ہجے کی تجاویز',
456
464
  noSuggestions: 'کوئی تجویز نہیں',
457
465
  },
466
+ autoComplete: {
467
+ label: 'تجاویز',
468
+ },
469
+ thesaurus: {
470
+ synonyms: 'مترادفات',
471
+ loading: 'مترادفات لوڈ ہو رہے ہیں…',
472
+ none: 'کوئی مترادف نہیں',
473
+ },
458
474
  link: {
459
475
  menuLabel: 'لنک',
460
476
  noUrl: '(کوئی یو آر ایل نہیں)',
@@ -723,6 +739,14 @@ const paShahmukhi = {
723
739
  spelling: 'ہجے دیاں تجویزاں',
724
740
  noSuggestions: 'کوئی تجویز نئیں',
725
741
  },
742
+ autoComplete: {
743
+ label: 'تجویزاں',
744
+ },
745
+ thesaurus: {
746
+ synonyms: 'ہم معنی لفظ',
747
+ loading: 'ہم معنی لبھ رہے نیں…',
748
+ none: 'کوئی ہم معنی نئیں',
749
+ },
726
750
  link: {
727
751
  menuLabel: 'لنک',
728
752
  noUrl: '(کوئی یو آر ایل نئیں)',
package/dist/index.d.ts CHANGED
@@ -16,4 +16,6 @@ export type { UrduNormalizationOptions } from './normalization/urduNormalize.js'
16
16
  export type { PunctuationOptions, PunctuationRule } from './autocorrect/punctuationRules.js';
17
17
  export { localStorageUserWordStore, apiUserWordStore, ignoreWord, addUserWord, onSpellWordsChange } from './spellcheck/userWords.js';
18
18
  export type { UserWordStore } from './spellcheck/userWords.js';
19
+ export { dictionaryCompletionStore, apiCompletionStore } from './autocomplete/completionStores.js';
20
+ export type { CompletionStore } from './autocomplete/completionStores.js';
19
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACrI,OAAO,EAAE,4BAA4B,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC1H,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAChI,YAAY,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAClI,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACrI,OAAO,EAAE,4BAA4B,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC1H,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAChI,YAAY,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAClI,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAChG,YAAY,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC"}
package/dist/index.js CHANGED
@@ -7,3 +7,4 @@ export { registerSpellDictionary, hasSpellDictionary } from './spellcheck/spellD
7
7
  export { localStorageAutoCorrectStore, apiAutoCorrectStore, fileAutoCorrectStore } from './autocorrect/autoCorrectStores.js';
8
8
  export { normalizeUrdu, normalizeUrduCharacters, removeUrduDiacritics, replaceUrduDigits } from './normalization/urduNormalize.js';
9
9
  export { localStorageUserWordStore, apiUserWordStore, ignoreWord, addUserWord, onSpellWordsChange } from './spellcheck/userWords.js';
10
+ export { dictionaryCompletionStore, apiCompletionStore } from './autocomplete/completionStores.js';
@@ -27,6 +27,10 @@ export declare function hasSpellDictionary(language: SpellLanguage): boolean;
27
27
  * can skip spellchecking that language rather than flag every word.
28
28
  */
29
29
  export declare function getSpeller(language: SpellLanguage): Promise<Speller> | null;
30
+ /** The dictionary's root words (its .dic entries without affix flags), for completion. */
31
+ export declare function dictionaryWords(dic: string): string[];
32
+ /** The language's dictionary words, or null when no dictionary is registered. */
33
+ export declare function getDictionaryWords(language: SpellLanguage): Promise<string[]> | null;
30
34
  /** A word in a text, with its position as character offsets. */
31
35
  export interface WordSpan {
32
36
  word: string;
@@ -1 +1 @@
1
- {"version":3,"file":"spellDictionaries.d.ts","sourceRoot":"","sources":["../../src/spellcheck/spellDictionaries.ts"],"names":[],"mappings":"AAGA,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,cAAc,CAAC;AAEzD,wFAAwF;AACxF,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACjC;AAED,KAAK,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;AAmB3C;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAGpG;AAED,2DAA2D;AAC3D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAe3E;AAED,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AASD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAMhD;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,EAAE,CAEzE"}
1
+ {"version":3,"file":"spellDictionaries.d.ts","sourceRoot":"","sources":["../../src/spellcheck/spellDictionaries.ts"],"names":[],"mappings":"AAGA,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,cAAc,CAAC;AAEzD,wFAAwF;AACxF,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACjC;AAED,KAAK,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;AAoB3C;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAIpG;AAED,2DAA2D;AAC3D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAc3E;AAYD,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAOrD;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAGpF;AAED,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AASD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAMhD;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,EAAE,CAEzE"}
@@ -1,6 +1,7 @@
1
1
  import nspell from 'nspell';
2
2
  import { isAcceptedWord } from './userWords.js';
3
3
  const loaders = new Map();
4
+ const loadedFiles = new Map();
4
5
  const spellers = new Map();
5
6
  async function fetchText(url) {
6
7
  const response = await fetch(url);
@@ -23,6 +24,7 @@ loaders.set('en', englishLoader);
23
24
  export function registerSpellDictionary(language, files) {
24
25
  loaders.set(language, typeof files === 'function' ? files : async () => files);
25
26
  spellers.delete(language);
27
+ loadedFiles.delete(language);
26
28
  }
27
29
  /** Whether a dictionary is registered for the language. */
28
30
  export function hasSpellDictionary(language) {
@@ -34,12 +36,11 @@ export function hasSpellDictionary(language) {
34
36
  * can skip spellchecking that language rather than flag every word.
35
37
  */
36
38
  export function getSpeller(language) {
37
- const loader = loaders.get(language);
38
- if (!loader)
39
+ if (!loaders.has(language))
39
40
  return null;
40
41
  let speller = spellers.get(language);
41
42
  if (!speller) {
42
- speller = loader()
43
+ speller = filesFor(language)
43
44
  .then(({ aff, dic }) => nspell(aff, dic))
44
45
  .then((base) => ({
45
46
  // Words the user added or ignored count as correct, whatever the dictionary says.
@@ -50,6 +51,31 @@ export function getSpeller(language) {
50
51
  }
51
52
  return speller;
52
53
  }
54
+ /** The dictionary's files, loaded once per language and shared by the speller and the word list. */
55
+ function filesFor(language) {
56
+ let loaded = loadedFiles.get(language);
57
+ if (!loaded) {
58
+ loaded = loaders.get(language)();
59
+ loadedFiles.set(language, loaded);
60
+ }
61
+ return loaded;
62
+ }
63
+ /** The dictionary's root words (its .dic entries without affix flags), for completion. */
64
+ export function dictionaryWords(dic) {
65
+ const words = new Set();
66
+ for (const line of dic.split(/\r?\n/).slice(1)) {
67
+ const word = line.split('/')[0].trim();
68
+ if (word)
69
+ words.add(word);
70
+ }
71
+ return [...words];
72
+ }
73
+ /** The language's dictionary words, or null when no dictionary is registered. */
74
+ export function getDictionaryWords(language) {
75
+ if (!loaders.has(language))
76
+ return null;
77
+ return filesFor(language).then(({ dic }) => dictionaryWords(dic));
78
+ }
53
79
  /**
54
80
  * Letters and combining marks, with apostrophes inside a word kept. The
55
81
  * Unicode classes cover Arabic-script letters and their diacritics, so Urdu
@@ -24,6 +24,8 @@ export declare function apiUserWordStore(options: {
24
24
  headers?: Record<string, string>;
25
25
  fetch?: typeof fetch;
26
26
  }): UserWordStore;
27
+ /** The words the user has added to the dictionary for a language. */
28
+ export declare function acceptedWords(language: SpellLanguage): string[];
27
29
  /** Calls `listener` whenever accepted or ignored words change. Returns an unsubscribe function. */
28
30
  export declare function onSpellWordsChange(listener: () => void): () => void;
29
31
  /** Whether the speller should treat the word as correct: it is in the user's dictionary or ignored this session. */
@@ -1 +1 @@
1
- {"version":3,"file":"userWords.d.ts","sourceRoot":"","sources":["../../src/spellcheck/userWords.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AAoBD,0EAA0E;AAC1E,wBAAgB,yBAAyB,CAAC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,aAAa,CAoBvF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB,GAAG,aAAa,CAmBhB;AAWD,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAGnE;AAED,oHAAoH;AACpH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED,2EAA2E;AAC3E,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1E;AAED,sFAAsF;AACtF,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlH;AAWD,+DAA+D;AAC/D,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAED,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAUtE"}
1
+ {"version":3,"file":"userWords.d.ts","sourceRoot":"","sources":["../../src/spellcheck/userWords.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AAoBD,0EAA0E;AAC1E,wBAAgB,yBAAyB,CAAC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,aAAa,CAoBvF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB,GAAG,aAAa,CAmBhB;AAWD,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAE/D;AAED,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAGnE;AAED,oHAAoH;AACpH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED,2EAA2E;AAC3E,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1E;AAED,sFAAsF;AACtF,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlH;AAWD,+DAA+D;AAC/D,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAED,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAUtE"}
@@ -68,6 +68,10 @@ function notify() {
68
68
  for (const listener of listeners)
69
69
  listener();
70
70
  }
71
+ /** The words the user has added to the dictionary for a language. */
72
+ export function acceptedWords(language) {
73
+ return [...(accepted.get(language) ?? [])];
74
+ }
71
75
  /** Calls `listener` whenever accepted or ignored words change. Returns an unsubscribe function. */
72
76
  export function onSpellWordsChange(listener) {
73
77
  listeners.add(listener);
@@ -1055,3 +1055,25 @@
1055
1055
  cursor: default;
1056
1056
  }
1057
1057
 
1058
+ /* Autocomplete suggestions, shown under the caret while typing. */
1059
+ .likhari-autocomplete {
1060
+ z-index: 20;
1061
+ min-width: 160px;
1062
+ max-width: 280px;
1063
+ padding: 4px 0;
1064
+ border: 1px solid var(--editor-line);
1065
+ border-radius: 6px;
1066
+ background: var(--editor-paper-dim);
1067
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.16);
1068
+ font-size: 14px;
1069
+ }
1070
+
1071
+ .likhari-autocomplete-item {
1072
+ padding: 3px 10px;
1073
+ cursor: pointer;
1074
+ }
1075
+
1076
+ .likhari-autocomplete-item--active {
1077
+ background: var(--editor-accent-soft);
1078
+ }
1079
+
@@ -0,0 +1,45 @@
1
+ import type { SpellLanguage } from '../spellcheck/spellDictionaries.js';
2
+ /**
3
+ * Where synonyms come from. The host can pass several; their synonyms are merged
4
+ * in order, without repeats. A failing store is skipped.
5
+ */
6
+ export interface ThesaurusStore {
7
+ readonly id: string;
8
+ /** Synonyms of `word` in `language`. Returns [] when there are none. */
9
+ lookup(word: string, language: SpellLanguage): Promise<string[]>;
10
+ }
11
+ /** One WordNet index entry for a lemma in one part of speech: its synset offsets. */
12
+ interface IndexEntry {
13
+ pos: string;
14
+ offsets: string[];
15
+ }
16
+ /**
17
+ * Parses a WordNet `index.<pos>` file: lemma → the synsets it belongs to. Lines
18
+ * starting with a space are the licence header.
19
+ */
20
+ export declare function parseWordNetIndex(text: string, pos: string): Map<string, IndexEntry[]>;
21
+ /**
22
+ * Parses a WordNet `data.<pos>` file: synset offset → the words in that synset.
23
+ * Multi-word entries use underscores in WordNet; they are returned with spaces.
24
+ */
25
+ export declare function parseWordNetData(text: string): Map<string, string[]>;
26
+ /**
27
+ * English synonyms from WordNet. The database is about 35MB, so it is fetched
28
+ * from `baseUrl` on the first lookup, and only the files needed are read.
29
+ * Browsers cache the files after that.
30
+ */
31
+ export declare function wordNetThesaurusStore(options?: {
32
+ baseUrl?: string;
33
+ fetch?: typeof fetch;
34
+ }): ThesaurusStore;
35
+ /** Synonyms from a service. `GET {url}?word=…&language=xx` returns a JSON array of strings. */
36
+ export declare function apiThesaurusStore(options: {
37
+ id?: string;
38
+ url: string;
39
+ headers?: Record<string, string>;
40
+ fetch?: typeof fetch;
41
+ }): ThesaurusStore;
42
+ /** Synonyms of `word` from all stores, merged in order without repeats, at most `limit`. */
43
+ export declare function lookupSynonyms(stores: ThesaurusStore[], word: string, language: SpellLanguage, limit?: number): Promise<string[]>;
44
+ export {};
45
+ //# sourceMappingURL=thesaurusStores.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thesaurusStores.d.ts","sourceRoot":"","sources":["../../src/thesaurus/thesaurusStores.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAClE;AAID,qFAAqF;AACrF,UAAU,UAAU;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAetF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAgBpE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CAAO,GAAG,cAAc,CAkD9G;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB,GAAG,cAAc,CAYjB;AAED,4FAA4F;AAC5F,wBAAsB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQnI"}
@@ -0,0 +1,131 @@
1
+ const POS_FILES = ['noun', 'verb', 'adj', 'adv'];
2
+ /**
3
+ * Parses a WordNet `index.<pos>` file: lemma → the synsets it belongs to. Lines
4
+ * starting with a space are the licence header.
5
+ */
6
+ export function parseWordNetIndex(text, pos) {
7
+ const entries = new Map();
8
+ for (const line of text.split('\n')) {
9
+ if (line === '' || line.startsWith(' '))
10
+ continue;
11
+ const fields = line.split(' ');
12
+ const lemma = fields[0];
13
+ const synsetCount = Number(fields[2]);
14
+ const pointerCount = Number(fields[3]);
15
+ // lemma pos synset_cnt p_cnt [pointer...] sense_cnt tagsense_cnt offset...
16
+ const offsets = fields.slice(4 + pointerCount + 2, 4 + pointerCount + 2 + synsetCount);
17
+ const list = entries.get(lemma) ?? [];
18
+ list.push({ pos, offsets });
19
+ entries.set(lemma, list);
20
+ }
21
+ return entries;
22
+ }
23
+ /**
24
+ * Parses a WordNet `data.<pos>` file: synset offset → the words in that synset.
25
+ * Multi-word entries use underscores in WordNet; they are returned with spaces.
26
+ */
27
+ export function parseWordNetData(text) {
28
+ const synsets = new Map();
29
+ for (const line of text.split('\n')) {
30
+ if (line === '' || line.startsWith(' '))
31
+ continue;
32
+ const fields = line.split(' ');
33
+ const offset = fields[0];
34
+ const wordCount = parseInt(fields[3], 16);
35
+ const words = [];
36
+ for (let i = 0; i < wordCount; i++) {
37
+ // Each word is followed by its lex_id; a "(a)"-style marker is not part of the word.
38
+ const word = fields[4 + i * 2].replace(/\(.*\)$/, '').replace(/_/g, ' ');
39
+ words.push(word);
40
+ }
41
+ synsets.set(offset, words);
42
+ }
43
+ return synsets;
44
+ }
45
+ /**
46
+ * English synonyms from WordNet. The database is about 35MB, so it is fetched
47
+ * from `baseUrl` on the first lookup, and only the files needed are read.
48
+ * Browsers cache the files after that.
49
+ */
50
+ export function wordNetThesaurusStore(options = {}) {
51
+ const base = options.baseUrl ?? 'https://cdn.jsdelivr.net/npm/wordnet-db@3.1.14/dict/';
52
+ const doFetch = options.fetch ?? fetch;
53
+ const indexes = new Map();
54
+ const datas = new Map();
55
+ const text = async (file) => {
56
+ const response = await doFetch(`${base}${file}`);
57
+ if (!response.ok)
58
+ throw new Error(`WordNet file failed to load: ${file}`);
59
+ return response.text();
60
+ };
61
+ const indexFor = (pos) => {
62
+ let loaded = indexes.get(pos);
63
+ if (!loaded) {
64
+ loaded = text(`index.${pos}`).then((t) => parseWordNetIndex(t, pos));
65
+ indexes.set(pos, loaded);
66
+ }
67
+ return loaded;
68
+ };
69
+ const dataFor = (pos) => {
70
+ let loaded = datas.get(pos);
71
+ if (!loaded) {
72
+ loaded = text(`data.${pos}`).then(parseWordNetData);
73
+ datas.set(pos, loaded);
74
+ }
75
+ return loaded;
76
+ };
77
+ return {
78
+ id: 'wordnet',
79
+ async lookup(word, language) {
80
+ if (language !== 'en')
81
+ return [];
82
+ const lemma = word.trim().toLowerCase().replace(/ /g, '_');
83
+ if (lemma === '')
84
+ return [];
85
+ const synonyms = new Set();
86
+ for (const pos of POS_FILES) {
87
+ const entries = (await indexFor(pos)).get(lemma);
88
+ if (!entries)
89
+ continue;
90
+ const synsets = await dataFor(pos);
91
+ for (const entry of entries) {
92
+ for (const offset of entry.offsets) {
93
+ for (const synonym of synsets.get(offset) ?? []) {
94
+ if (synonym.toLowerCase() !== lemma.replace(/_/g, ' '))
95
+ synonyms.add(synonym);
96
+ }
97
+ }
98
+ }
99
+ }
100
+ return [...synonyms];
101
+ },
102
+ };
103
+ }
104
+ /** Synonyms from a service. `GET {url}?word=…&language=xx` returns a JSON array of strings. */
105
+ export function apiThesaurusStore(options) {
106
+ const doFetch = options.fetch ?? fetch;
107
+ return {
108
+ id: options.id ?? 'api',
109
+ async lookup(word, language) {
110
+ const query = `?word=${encodeURIComponent(word)}&language=${encodeURIComponent(language)}`;
111
+ const response = await doFetch(`${options.url}${query}`, { headers: options.headers });
112
+ if (!response.ok)
113
+ throw new Error(`Thesaurus API failed: ${response.status}`);
114
+ const data = await response.json();
115
+ return Array.isArray(data) ? data.filter((s) => typeof s === 'string') : [];
116
+ },
117
+ };
118
+ }
119
+ /** Synonyms of `word` from all stores, merged in order without repeats, at most `limit`. */
120
+ export async function lookupSynonyms(stores, word, language, limit = 10) {
121
+ const results = await Promise.allSettled(stores.map((store) => store.lookup(word, language)));
122
+ const merged = new Set();
123
+ for (const result of results) {
124
+ if (result.status !== 'fulfilled')
125
+ continue;
126
+ for (const synonym of result.value)
127
+ if (synonym !== word)
128
+ merged.add(synonym);
129
+ }
130
+ return [...merged].slice(0, limit);
131
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.25",
3
+ "version": "0.1.26-dev.242",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {