@inshapardaz/likhari-react 0.1.25 → 0.1.26-dev.240
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EditorRoot.d.ts +9 -0
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +11 -3
- package/dist/autocomplete/AutocompletePlugin.d.ts +16 -0
- package/dist/autocomplete/AutocompletePlugin.d.ts.map +1 -0
- package/dist/autocomplete/AutocompletePlugin.js +285 -0
- package/dist/autocomplete/completionStores.d.ts +34 -0
- package/dist/autocomplete/completionStores.d.ts.map +1 -0
- package/dist/autocomplete/completionStores.js +78 -0
- package/dist/autocomplete/wordIndex.d.ts +20 -0
- package/dist/autocomplete/wordIndex.d.ts.map +1 -0
- package/dist/autocomplete/wordIndex.js +51 -0
- package/dist/components/Toolbar.d.ts +4 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +22 -6
- package/dist/i18n/strings.d.ts +8 -0
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +24 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/spellcheck/spellDictionaries.d.ts +4 -0
- package/dist/spellcheck/spellDictionaries.d.ts.map +1 -1
- package/dist/spellcheck/spellDictionaries.js +29 -3
- package/dist/spellcheck/userWords.d.ts +2 -0
- package/dist/spellcheck/userWords.d.ts.map +1 -1
- package/dist/spellcheck/userWords.js +4 -0
- package/dist/theme/editor.css +22 -0
- package/dist/thesaurus/thesaurusStores.d.ts +45 -0
- package/dist/thesaurus/thesaurusStores.d.ts.map +1 -0
- package/dist/thesaurus/thesaurusStores.js +131 -0
- package/package.json +1 -1
package/dist/EditorRoot.d.ts
CHANGED
|
@@ -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. */
|
package/dist/EditorRoot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAY1D,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEjF,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAOjE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAIhF,OAAO,EAAgC,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACtG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAY1D,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEjF,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAOjE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAIhF,OAAO,EAAgC,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACtG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,6BAA6B,CAAC;AACzF,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,EAA4C,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACtG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAYpE,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMnE,yEAAyE;AACzE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,YAAY,GAAG,KAAK,CAAC;AAEnE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,QAAQ,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAaD,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mGAAmG;IACnG,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC,sGAAsG;IACtG,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;IACnC,yIAAyI;IACzI,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,mGAAmG;IACnG,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,iHAAiH;IACjH,oBAAoB,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IAC9C,gHAAgH;IAChH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAC7C,wFAAwF;IACxF,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAClD;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC;;;;;;;;;;;;;;;OAeG;IACH,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,uGAAuG;IACvG,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3E,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IAClD,iBAAiB,IAAI,OAAO,CAAC;IAC7B,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAQD,eAAO,MAAM,UAAU,uGAybrB,CAAC"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
525
|
-
|
|
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?.
|
|
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: {
|
package/dist/i18n/strings.d.ts
CHANGED
|
@@ -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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"}
|
package/dist/i18n/strings.js
CHANGED
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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 =
|
|
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);
|
package/dist/theme/editor.css
CHANGED
|
@@ -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
|
+
}
|