@inshapardaz/likhari-react 0.1.23 → 0.1.25-dev.224
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 +3 -0
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +16 -3
- package/dist/autocomplete/AutocompletePlugin.d.ts +11 -0
- package/dist/autocomplete/AutocompletePlugin.d.ts.map +1 -0
- package/dist/autocomplete/AutocompletePlugin.js +180 -0
- package/dist/autocomplete/wordIndex.d.ts +13 -0
- package/dist/autocomplete/wordIndex.d.ts.map +1 -0
- package/dist/autocomplete/wordIndex.js +37 -0
- package/dist/autocorrect/AutoCorrectPlugin.d.ts.map +1 -1
- package/dist/autocorrect/AutoCorrectPlugin.js +18 -2
- package/dist/autocorrect/autoCorrectActions.d.ts +6 -0
- package/dist/autocorrect/autoCorrectActions.d.ts.map +1 -1
- package/dist/autocorrect/autoCorrectActions.js +30 -0
- package/dist/components/AutoCorrectPanel.d.ts +5 -1
- package/dist/components/AutoCorrectPanel.d.ts.map +1 -1
- package/dist/components/AutoCorrectPanel.js +3 -3
- package/dist/components/SpellcheckPanel.d.ts +5 -1
- package/dist/components/SpellcheckPanel.d.ts.map +1 -1
- package/dist/components/SpellcheckPanel.js +12 -2
- package/dist/components/Toolbar.d.ts +7 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +20 -4
- package/dist/i18n/strings.d.ts +6 -0
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +18 -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/SpellHighlightPlugin.d.ts.map +1 -1
- package/dist/spellcheck/SpellHighlightPlugin.js +3 -0
- package/dist/spellcheck/spellDictionaries.d.ts +4 -0
- package/dist/spellcheck/spellDictionaries.d.ts.map +1 -1
- package/dist/spellcheck/spellDictionaries.js +36 -3
- package/dist/spellcheck/userWords.d.ts +41 -0
- package/dist/spellcheck/userWords.d.ts.map +1 -0
- package/dist/spellcheck/userWords.js +136 -0
- package/dist/theme/editor.css +22 -0
- package/package.json +1 -1
package/dist/EditorRoot.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ 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 UserWordStore } from './spellcheck/userWords.js';
|
|
10
11
|
import { type Locale } from './i18n/index.js';
|
|
11
12
|
/** What happens when the user is about to leave with unsaved changes. */
|
|
12
13
|
export type NavigationGuardMode = 'confirm' | 'save-draft' | 'off';
|
|
@@ -18,6 +19,8 @@ export interface EditorRootProps {
|
|
|
18
19
|
documentId?: string;
|
|
19
20
|
/** Where auto-corrections are loaded from and saved to, in priority order. Pass a stable array. */
|
|
20
21
|
autoCorrectStores?: AutoCorrectStore[];
|
|
22
|
+
/** Where words added to the spelling dictionary are saved, in priority order. Pass a stable array. */
|
|
23
|
+
dictionaryStores?: UserWordStore[];
|
|
21
24
|
/** Urdu normalisation applied as part of auto-correct. Diacritics are kept unless `removeDiacritics` is set. */
|
|
22
25
|
urduNormalization?: UrduNormalizationOptions;
|
|
23
26
|
/** 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":"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"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -32,6 +32,8 @@ 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 { loadUserWords, localStorageUserWordStore } from './spellcheck/userWords.js';
|
|
35
37
|
import { LeaveDialog } from './components/LeaveDialog.js';
|
|
36
38
|
import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
|
|
37
39
|
import { ImageOptionsContext } from './image/ImageOptionsContext.js';
|
|
@@ -42,13 +44,15 @@ const DEFAULT_AUTOSAVE_MAX_BYTES = 2000000;
|
|
|
42
44
|
const DEFAULT_AUTOSAVE_MAX_DRAFTS = 20;
|
|
43
45
|
/** Used when the host passes no stores: corrections are kept in this browser. */
|
|
44
46
|
const DEFAULT_AUTOCORRECT_STORES = [localStorageAutoCorrectStore()];
|
|
47
|
+
/** Used when the host passes no dictionary stores: added words are kept in this browser. */
|
|
48
|
+
const DEFAULT_DICTIONARY_STORES = [localStorageUserWordStore()];
|
|
45
49
|
function initialEditorStateJson(initialContent) {
|
|
46
50
|
if (!initialContent)
|
|
47
51
|
return undefined;
|
|
48
52
|
const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
|
|
49
53
|
return JSON.stringify(state);
|
|
50
54
|
}
|
|
51
|
-
export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorrectStores, 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
|
+
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) {
|
|
52
56
|
const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
|
|
53
57
|
const strings = useMemo(() => getStrings(locale), [locale]);
|
|
54
58
|
const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
|
|
@@ -76,8 +80,17 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorre
|
|
|
76
80
|
const lastSavedJsonRef = useRef(null);
|
|
77
81
|
const [isDirty, setIsDirty] = useState(false);
|
|
78
82
|
const [openPanel, setOpenPanel] = useState(null);
|
|
83
|
+
const [autoCorrectPrefill, setAutoCorrectPrefill] = useState(null);
|
|
84
|
+
const openAutoCorrectFor = (from, language) => {
|
|
85
|
+
setAutoCorrectPrefill({ id: Date.now(), from, language });
|
|
86
|
+
setOpenPanel('autocorrect');
|
|
87
|
+
};
|
|
79
88
|
const [autoCorrectVersion, setAutoCorrectVersion] = useState(0);
|
|
80
89
|
const stores = autoCorrectStores ?? DEFAULT_AUTOCORRECT_STORES;
|
|
90
|
+
const wordStores = dictionaryStores ?? DEFAULT_DICTIONARY_STORES;
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
void loadUserWords(wordStores);
|
|
93
|
+
}, [wordStores]);
|
|
81
94
|
const togglePanel = (panel) => setOpenPanel((current) => (current === panel ? null : panel));
|
|
82
95
|
const isDirtyRef = useRef(isDirty);
|
|
83
96
|
isDirtyRef.current = isDirty;
|
|
@@ -291,9 +304,9 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorre
|
|
|
291
304
|
// rather than colliding on a shared one.
|
|
292
305
|
const portalTargetId = useId();
|
|
293
306
|
const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
|
|
294
|
-
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, 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
|
+
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
|
|
295
308
|
? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
|
|
296
|
-
: 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, onClose: () => setOpenPanel(null) })), config.language.autocorrect && openPanel === 'autocorrect' && (_jsx(AutoCorrectPanel, { strings: strings, dir: dir, stores: stores, onSaved: () => setAutoCorrectVersion((v) => v + 1), onClose: () => setOpenPanel(null) })), _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: () => {
|
|
309
|
+
: 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 }), (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: () => {
|
|
297
310
|
handleSave();
|
|
298
311
|
settleLeave(true);
|
|
299
312
|
}, onSaveDraft: () => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggests completions for the word being typed, from the dictionary's words and
|
|
3
|
+
* the user's own. Shown only while typing, at the caret. Tab or Enter accepts the
|
|
4
|
+
* highlighted suggestion, the arrow keys move through the list, and Escape (or
|
|
5
|
+
* moving the caret) dismisses it. Left-to-right text uses the English dictionary,
|
|
6
|
+
* right-to-left text the Urdu one.
|
|
7
|
+
*/
|
|
8
|
+
export declare function AutocompletePlugin({ label }: {
|
|
9
|
+
label: string;
|
|
10
|
+
}): import("react").JSX.Element | null;
|
|
11
|
+
//# sourceMappingURL=AutocompletePlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AutocompletePlugin.d.ts","sourceRoot":"","sources":["../../src/autocomplete/AutocompletePlugin.tsx"],"names":[],"mappings":"AAgCA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,sCA4J9D"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, 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_TAB_COMMAND, } from 'lexical';
|
|
5
|
+
import { $replaceMatch } from '../find/findReplaceActions.js';
|
|
6
|
+
import { getDictionaryWords } from '../spellcheck/spellDictionaries.js';
|
|
7
|
+
import { acceptedWords, onSpellWordsChange } from '../spellcheck/userWords.js';
|
|
8
|
+
import { WordIndex } from './wordIndex.js';
|
|
9
|
+
/** The letters at the end of the text before the caret. */
|
|
10
|
+
const WORD_END = /[\p{L}\p{M}]+$/u;
|
|
11
|
+
const LETTER = /[\p{L}\p{M}]/u;
|
|
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 }) {
|
|
22
|
+
const [editor] = useLexicalComposerContext();
|
|
23
|
+
const [popup, setPopup] = useState(null);
|
|
24
|
+
const popupRef = useRef(null);
|
|
25
|
+
const typing = useRef(false);
|
|
26
|
+
const indexes = useRef(new Map());
|
|
27
|
+
const show = (next) => {
|
|
28
|
+
popupRef.current = next;
|
|
29
|
+
setPopup(next);
|
|
30
|
+
};
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const indexFor = (language) => {
|
|
33
|
+
let index = indexes.current.get(language);
|
|
34
|
+
if (!index) {
|
|
35
|
+
const words = getDictionaryWords(language);
|
|
36
|
+
if (!words)
|
|
37
|
+
return null;
|
|
38
|
+
index = words.then((dictionary) => new WordIndex([...dictionary, ...acceptedWords(language)]));
|
|
39
|
+
indexes.current.set(language, index);
|
|
40
|
+
}
|
|
41
|
+
return index;
|
|
42
|
+
};
|
|
43
|
+
const hide = () => show(null);
|
|
44
|
+
const refresh = () => {
|
|
45
|
+
if (!typing.current)
|
|
46
|
+
return hide();
|
|
47
|
+
let target = null;
|
|
48
|
+
editor.getEditorState().read(() => {
|
|
49
|
+
const selection = $getSelection();
|
|
50
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
|
51
|
+
return;
|
|
52
|
+
const node = selection.anchor.getNode();
|
|
53
|
+
if (!$isTextNode(node))
|
|
54
|
+
return;
|
|
55
|
+
const text = node.getTextContent();
|
|
56
|
+
const before = text.slice(0, selection.anchor.offset);
|
|
57
|
+
const prefix = WORD_END.exec(before)?.[0];
|
|
58
|
+
const after = text.charAt(selection.anchor.offset);
|
|
59
|
+
if (!prefix || prefix.length < MIN_PREFIX || (after !== '' && LETTER.test(after)))
|
|
60
|
+
return;
|
|
61
|
+
const rtl = node.getParentOrThrow().getDirection() === 'rtl';
|
|
62
|
+
target = { prefix, language: rtl ? 'ur' : 'en', dir: rtl ? 'rtl' : 'ltr' };
|
|
63
|
+
});
|
|
64
|
+
if (target === null)
|
|
65
|
+
return hide();
|
|
66
|
+
const { prefix, language, dir } = target;
|
|
67
|
+
const index = indexFor(language);
|
|
68
|
+
if (!index)
|
|
69
|
+
return hide();
|
|
70
|
+
void index.then((loaded) => {
|
|
71
|
+
const items = loaded.complete(prefix);
|
|
72
|
+
if (items.length === 0)
|
|
73
|
+
return hide();
|
|
74
|
+
const rect = caretRect(editor);
|
|
75
|
+
show({ items, index: 0, x: rect.left, y: rect.bottom, dir });
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const move = (delta) => {
|
|
79
|
+
const current = popupRef.current;
|
|
80
|
+
if (!current)
|
|
81
|
+
return false;
|
|
82
|
+
const count = current.items.length;
|
|
83
|
+
show({ ...current, index: (current.index + delta + count) % count });
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
const onKeyDown = (event) => {
|
|
87
|
+
if (event.key === 'Escape' && popupRef.current) {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
show(null);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Printable keys and Backspace are typing; any other key (arrows, Home, ...) moves the caret.
|
|
93
|
+
typing.current = (event.key.length === 1 && !event.ctrlKey && !event.metaKey) || event.key === 'Backspace';
|
|
94
|
+
if (!typing.current)
|
|
95
|
+
hide();
|
|
96
|
+
setTimeout(refresh, 0);
|
|
97
|
+
};
|
|
98
|
+
const onMouseDown = () => {
|
|
99
|
+
typing.current = false;
|
|
100
|
+
hide();
|
|
101
|
+
};
|
|
102
|
+
let root = null;
|
|
103
|
+
const attach = (next) => {
|
|
104
|
+
root?.removeEventListener('keydown', onKeyDown);
|
|
105
|
+
root?.removeEventListener('mousedown', onMouseDown);
|
|
106
|
+
root = next;
|
|
107
|
+
root?.addEventListener('keydown', onKeyDown);
|
|
108
|
+
root?.addEventListener('mousedown', onMouseDown);
|
|
109
|
+
};
|
|
110
|
+
const unregisterRoot = editor.registerRootListener((next) => attach(next));
|
|
111
|
+
const unregisterUpdate = editor.registerUpdateListener(() => {
|
|
112
|
+
// Keydown already schedules a refresh; this keeps the list right after the
|
|
113
|
+
// dictionary or user words change.
|
|
114
|
+
if (typing.current)
|
|
115
|
+
setTimeout(refresh, 0);
|
|
116
|
+
});
|
|
117
|
+
const unregisterWords = onSpellWordsChange(() => {
|
|
118
|
+
indexes.current.clear();
|
|
119
|
+
if (typing.current)
|
|
120
|
+
setTimeout(refresh, 0);
|
|
121
|
+
});
|
|
122
|
+
const unregisterCommands = [
|
|
123
|
+
editor.registerCommand(KEY_ARROW_DOWN_COMMAND, () => move(1), COMMAND_PRIORITY_HIGH),
|
|
124
|
+
editor.registerCommand(KEY_ARROW_UP_COMMAND, () => move(-1), COMMAND_PRIORITY_HIGH),
|
|
125
|
+
editor.registerCommand(KEY_TAB_COMMAND, (event) => acceptHighlighted(event), COMMAND_PRIORITY_HIGH),
|
|
126
|
+
editor.registerCommand(KEY_ENTER_COMMAND, (event) => acceptHighlighted(event), COMMAND_PRIORITY_HIGH),
|
|
127
|
+
];
|
|
128
|
+
function acceptHighlighted(event) {
|
|
129
|
+
const current = popupRef.current;
|
|
130
|
+
if (!current)
|
|
131
|
+
return false;
|
|
132
|
+
event?.preventDefault();
|
|
133
|
+
acceptItem(editor, current.items[current.index]);
|
|
134
|
+
show(null);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
return () => {
|
|
138
|
+
unregisterRoot();
|
|
139
|
+
unregisterUpdate();
|
|
140
|
+
unregisterWords();
|
|
141
|
+
unregisterCommands.forEach((unregister) => unregister());
|
|
142
|
+
attach(null);
|
|
143
|
+
show(null);
|
|
144
|
+
};
|
|
145
|
+
}, [editor]);
|
|
146
|
+
if (!popup)
|
|
147
|
+
return null;
|
|
148
|
+
return (_jsx("div", { className: "likhari-autocomplete", role: "listbox", "aria-label": label, dir: popup.dir, style: { position: 'fixed', left: popup.x, top: popup.y + 4 }, 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) => {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
acceptItem(editor, item);
|
|
151
|
+
}, children: item }, item))) }));
|
|
152
|
+
}
|
|
153
|
+
/** Where to put the popup: under the caret, or the editor's top-left if the caret has no position yet. */
|
|
154
|
+
function caretRect(editor) {
|
|
155
|
+
const selection = window.getSelection();
|
|
156
|
+
if (selection && selection.rangeCount > 0) {
|
|
157
|
+
const rect = selection.getRangeAt(0).getBoundingClientRect();
|
|
158
|
+
if (rect.width > 0 || rect.height > 0)
|
|
159
|
+
return { left: rect.left, bottom: rect.bottom };
|
|
160
|
+
}
|
|
161
|
+
const root = editor.getRootElement()?.getBoundingClientRect();
|
|
162
|
+
return { left: root?.left ?? 0, bottom: root?.top ?? 0 };
|
|
163
|
+
}
|
|
164
|
+
function acceptItem(editor, item) {
|
|
165
|
+
editor.update(() => {
|
|
166
|
+
const selection = $getSelection();
|
|
167
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
|
168
|
+
return;
|
|
169
|
+
const node = selection.anchor.getNode();
|
|
170
|
+
if (!$isTextNode(node))
|
|
171
|
+
return;
|
|
172
|
+
const before = node.getTextContent().slice(0, selection.anchor.offset);
|
|
173
|
+
const prefix = WORD_END.exec(before)?.[0];
|
|
174
|
+
if (!prefix)
|
|
175
|
+
return;
|
|
176
|
+
const start = before.length - prefix.length;
|
|
177
|
+
$replaceMatch({ anchorKey: node.getKey(), anchorOffset: start, focusKey: node.getKey(), focusOffset: before.length }, item);
|
|
178
|
+
}, { discrete: true });
|
|
179
|
+
editor.focus();
|
|
180
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Words sorted once, so every prefix lookup is a binary search rather than a scan. */
|
|
2
|
+
export declare class WordIndex {
|
|
3
|
+
private readonly words;
|
|
4
|
+
constructor(words: Iterable<string>);
|
|
5
|
+
/**
|
|
6
|
+
* Completions for a prefix: words that start with it (other than the prefix
|
|
7
|
+
* itself), shortest first, then alphabetical. Only the first `scanLimit`
|
|
8
|
+
* matches are considered, so a one-letter prefix stays quick.
|
|
9
|
+
*/
|
|
10
|
+
complete(prefix: string, limit?: number, scanLimit?: number): string[];
|
|
11
|
+
private lowerBound;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=wordIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wordIndex.d.ts","sourceRoot":"","sources":["../../src/autocomplete/wordIndex.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;gBAErB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC;IAInC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAI,EAAE,SAAS,SAAM,GAAG,MAAM,EAAE;IAY9D,OAAO,CAAC,UAAU;CAUnB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Words sorted once, so every prefix lookup is a binary search rather than a scan. */
|
|
2
|
+
export class WordIndex {
|
|
3
|
+
constructor(words) {
|
|
4
|
+
this.words = [...new Set(words)].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Completions for a prefix: words that start with it (other than the prefix
|
|
8
|
+
* itself), shortest first, then alphabetical. Only the first `scanLimit`
|
|
9
|
+
* matches are considered, so a one-letter prefix stays quick.
|
|
10
|
+
*/
|
|
11
|
+
complete(prefix, limit = 5, scanLimit = 500) {
|
|
12
|
+
if (prefix === '')
|
|
13
|
+
return [];
|
|
14
|
+
const matches = [];
|
|
15
|
+
for (let i = this.lowerBound(prefix); i < this.words.length && matches.length < scanLimit; i++) {
|
|
16
|
+
const word = this.words[i];
|
|
17
|
+
if (!word.startsWith(prefix))
|
|
18
|
+
break;
|
|
19
|
+
if (word !== prefix)
|
|
20
|
+
matches.push(word);
|
|
21
|
+
}
|
|
22
|
+
matches.sort((a, b) => a.length - b.length || (a < b ? -1 : a > b ? 1 : 0));
|
|
23
|
+
return matches.slice(0, limit);
|
|
24
|
+
}
|
|
25
|
+
lowerBound(prefix) {
|
|
26
|
+
let low = 0;
|
|
27
|
+
let high = this.words.length;
|
|
28
|
+
while (low < high) {
|
|
29
|
+
const mid = (low + high) >>> 1;
|
|
30
|
+
if (this.words[mid] < prefix)
|
|
31
|
+
low = mid + 1;
|
|
32
|
+
else
|
|
33
|
+
high = mid;
|
|
34
|
+
}
|
|
35
|
+
return low;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoCorrectPlugin.d.ts","sourceRoot":"","sources":["../../src/autocorrect/AutoCorrectPlugin.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"AutoCorrectPlugin.d.ts","sourceRoot":"","sources":["../../src/autocorrect/AutoCorrectPlugin.tsx"],"names":[],"mappings":"AAEA,OAAO,EAQL,KAAK,cAAc,EACpB,MAAM,SAAS,CAAC;AAWjB,OAAO,EAA0B,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAiB,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAG9F,sFAAsF;AACtF,kFAAkF;AAClF,eAAO,MAAM,wBAAwB,EAAE,cAAc,CAAC,IAAI,CAA6C,CAAC;AAMxG;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,iBAAiB,EACjB,WAAW,GACZ,EAAE;IACD,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,OAAO,EAAE,OAAO,CAAC;IACjB,oFAAoF;IACpF,MAAM,EAAE,OAAO,CAAC;IAChB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAC7C,kEAAkE;IAClE,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC,QA0GA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
-
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical';
|
|
4
|
-
import { $correctDocument, $correctPunctuationBeforeCaret, $correctPunctuationDocument, $correctWordBeforeCaret, $preservingSelection, $spacePunctuationDocument, } from './autoCorrectActions.js';
|
|
3
|
+
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR, BLUR_COMMAND, COMMAND_PRIORITY_HIGH, KEY_ENTER_COMMAND, createCommand, } from 'lexical';
|
|
4
|
+
import { $correctDocument, $correctPunctuationBeforeCaret, $correctPunctuationDocument, $correctWordBeforeCaret, $correctWordEndingAtCaret, $preservingSelection, $spacePunctuationDocument, } from './autoCorrectActions.js';
|
|
5
5
|
import { $cleanUpText } from '../cleanup/textCleanupActions.js';
|
|
6
6
|
import { activePunctuationRules } from './punctuationRules.js';
|
|
7
7
|
import { loadAutoCorrections } from './autoCorrectStores.js';
|
|
@@ -99,8 +99,24 @@ export function AutoCorrectPlugin({ stores, version, enabled, typing, urduNormal
|
|
|
99
99
|
root?.addEventListener('keydown', onKeyDown);
|
|
100
100
|
};
|
|
101
101
|
const unregister = editor.registerRootListener((next) => attach(next));
|
|
102
|
+
// A word with nothing after it (end of line or document) is corrected when the
|
|
103
|
+
// caret leaves it: Enter splits the line, and blur means the user has moved on.
|
|
104
|
+
const correctEndingWord = () => {
|
|
105
|
+
const selection = $getSelection();
|
|
106
|
+
if (!$isRangeSelection(selection))
|
|
107
|
+
return false;
|
|
108
|
+
const rtl = selection.anchor.getNode().getTopLevelElement()?.getDirection() === 'rtl';
|
|
109
|
+
const table = tableFor(rtl);
|
|
110
|
+
if (table)
|
|
111
|
+
$correctWordEndingAtCaret(table, rtl ? normalizeRtl : undefined);
|
|
112
|
+
return false;
|
|
113
|
+
};
|
|
114
|
+
const unregisterEnter = editor.registerCommand(KEY_ENTER_COMMAND, correctEndingWord, COMMAND_PRIORITY_HIGH);
|
|
115
|
+
const unregisterBlur = editor.registerCommand(BLUR_COMMAND, correctEndingWord, COMMAND_PRIORITY_EDITOR);
|
|
102
116
|
return () => {
|
|
103
117
|
unregister();
|
|
118
|
+
unregisterEnter();
|
|
119
|
+
unregisterBlur();
|
|
104
120
|
attach(null);
|
|
105
121
|
};
|
|
106
122
|
}, [editor, enabled, typing]);
|
|
@@ -32,4 +32,10 @@ export declare function $correctPunctuationDocument(rules: PunctuationRule[]): n
|
|
|
32
32
|
export declare function $preservingSelection(change: () => void): void;
|
|
33
33
|
/** Applies urduhack-style punctuation spacing to every text node. Returns how many changed. */
|
|
34
34
|
export declare function $spacePunctuationDocument(): number;
|
|
35
|
+
/**
|
|
36
|
+
* Corrects the word that ends at the caret when nothing follows it, which is the
|
|
37
|
+
* end of a line: the caret is about to leave the line (Enter). The caret ends
|
|
38
|
+
* after the replacement.
|
|
39
|
+
*/
|
|
40
|
+
export declare function $correctWordEndingAtCaret(table: Map<string, string>, normalize?: (word: string) => string): boolean;
|
|
35
41
|
//# sourceMappingURL=autoCorrectActions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoCorrectActions.d.ts","sourceRoot":"","sources":["../../src/autocorrect/autoCorrectActions.ts"],"names":[],"mappings":"AAWA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAMhF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAiCjH;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACxB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACxB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACtC,MAAM,CA8BR;AAQD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,OAAO,CAqBhF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CA2B5E;AAID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI,CAoB7D;AAED,+FAA+F;AAC/F,wBAAgB,yBAAyB,IAAI,MAAM,CAWlD"}
|
|
1
|
+
{"version":3,"file":"autoCorrectActions.d.ts","sourceRoot":"","sources":["../../src/autocorrect/autoCorrectActions.ts"],"names":[],"mappings":"AAWA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAMhF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAiCjH;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACxB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACxB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACtC,MAAM,CA8BR;AAQD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,OAAO,CAqBhF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CA2B5E;AAID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI,CAoB7D;AAED,+FAA+F;AAC/F,wBAAgB,yBAAyB,IAAI,MAAM,CAWlD;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAuBnH"}
|
|
@@ -180,3 +180,33 @@ export function $spacePunctuationDocument() {
|
|
|
180
180
|
}
|
|
181
181
|
return changed;
|
|
182
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Corrects the word that ends at the caret when nothing follows it, which is the
|
|
185
|
+
* end of a line: the caret is about to leave the line (Enter). The caret ends
|
|
186
|
+
* after the replacement.
|
|
187
|
+
*/
|
|
188
|
+
export function $correctWordEndingAtCaret(table, normalize) {
|
|
189
|
+
const selection = $getSelection();
|
|
190
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
|
191
|
+
return false;
|
|
192
|
+
const anchor = selection.anchor;
|
|
193
|
+
const node = anchor.getNode();
|
|
194
|
+
if (!$isTextNode(node))
|
|
195
|
+
return false;
|
|
196
|
+
const before = node.getTextContent().slice(0, anchor.offset);
|
|
197
|
+
const word = WORD_END.exec(before)?.[0];
|
|
198
|
+
if (!word)
|
|
199
|
+
return false;
|
|
200
|
+
const replacement = correctionFor(word, table, normalize);
|
|
201
|
+
if (replacement === undefined || replacement === word)
|
|
202
|
+
return false;
|
|
203
|
+
const start = before.length - word.length;
|
|
204
|
+
$replaceMatch({ anchorKey: node.getKey(), anchorOffset: start, focusKey: node.getKey(), focusOffset: before.length }, replacement);
|
|
205
|
+
const after = $getSelection();
|
|
206
|
+
if ($isRangeSelection(after)) {
|
|
207
|
+
const caret = start + replacement.length;
|
|
208
|
+
after.anchor.set(node.getKey(), caret, 'text');
|
|
209
|
+
after.focus.set(node.getKey(), caret, 'text');
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { Strings } from '../i18n/strings.js';
|
|
2
2
|
import { type AutoCorrectStore } from '../autocorrect/autoCorrectStores.js';
|
|
3
|
+
import type { SpellLanguage } from '../spellcheck/spellDictionaries.js';
|
|
3
4
|
/**
|
|
4
5
|
* Adds one correction (a typed word and its replacement) to the first store
|
|
5
6
|
* that accepts writes. Saving bumps the editor's correction version, so the new
|
|
6
7
|
* entry applies to the next word the user types.
|
|
7
8
|
*/
|
|
8
|
-
export declare function AutoCorrectPanel({ strings, dir, stores, onSaved, onClose, }: {
|
|
9
|
+
export declare function AutoCorrectPanel({ strings, dir, stores, initialFrom, initialLanguage, onSaved, onClose, }: {
|
|
9
10
|
strings: Strings;
|
|
10
11
|
dir: 'ltr' | 'rtl';
|
|
11
12
|
stores: AutoCorrectStore[];
|
|
13
|
+
/** The typed word to start with, e.g. a misspelled word the user chose to correct. */
|
|
14
|
+
initialFrom?: string;
|
|
15
|
+
initialLanguage?: SpellLanguage;
|
|
12
16
|
onSaved: () => void;
|
|
13
17
|
onClose: () => void;
|
|
14
18
|
}): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoCorrectPanel.d.ts","sourceRoot":"","sources":["../../src/components/AutoCorrectPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"AutoCorrectPanel.d.ts","sourceRoot":"","sources":["../../src/components/AutoCorrectPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAC/F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAQrE;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,GAAG,EACH,MAAM,EACN,WAAgB,EAChB,eAAsB,EACtB,OAAO,EACP,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,KAAK,GAAG,KAAK,CAAC;IACnB,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,aAAa,CAAC;IAChC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAqEA"}
|
|
@@ -12,10 +12,10 @@ const LANGUAGES = [
|
|
|
12
12
|
* that accepts writes. Saving bumps the editor's correction version, so the new
|
|
13
13
|
* entry applies to the next word the user types.
|
|
14
14
|
*/
|
|
15
|
-
export function AutoCorrectPanel({ strings, dir, stores, onSaved, onClose, }) {
|
|
15
|
+
export function AutoCorrectPanel({ strings, dir, stores, initialFrom = '', initialLanguage = 'en', onSaved, onClose, }) {
|
|
16
16
|
const t = strings.autoCorrect;
|
|
17
|
-
const [language, setLanguage] = useState(
|
|
18
|
-
const [from, setFrom] = useState(
|
|
17
|
+
const [language, setLanguage] = useState(initialLanguage);
|
|
18
|
+
const [from, setFrom] = useState(initialFrom);
|
|
19
19
|
const [to, setTo] = useState('');
|
|
20
20
|
const [status, setStatus] = useState('idle');
|
|
21
21
|
const save = async () => {
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { Strings } from '../i18n/strings.js';
|
|
2
|
+
import { type SpellLanguage } from '../spellcheck/spellDictionaries.js';
|
|
3
|
+
import { type UserWordStore } from '../spellcheck/userWords.js';
|
|
2
4
|
/**
|
|
3
5
|
* Steps through the misspelled words one at a time, like the find widget.
|
|
4
6
|
* Each word is selected in the editor as it comes up, and its suggestions
|
|
5
7
|
* can replace it. Checking reads the document on demand, so it never flags
|
|
6
8
|
* words while the user is still typing.
|
|
7
9
|
*/
|
|
8
|
-
export declare function SpellcheckPanel({ strings, dir, onClose }: {
|
|
10
|
+
export declare function SpellcheckPanel({ strings, dir, dictionaryStores, onAddAutoCorrect, onClose, }: {
|
|
9
11
|
strings: Strings;
|
|
10
12
|
dir: 'ltr' | 'rtl';
|
|
13
|
+
dictionaryStores: UserWordStore[];
|
|
14
|
+
onAddAutoCorrect?: (word: string, language: SpellLanguage) => void;
|
|
11
15
|
onClose: () => void;
|
|
12
16
|
}): import("react").JSX.Element;
|
|
13
17
|
//# sourceMappingURL=SpellcheckPanel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpellcheckPanel.d.ts","sourceRoot":"","sources":["../../src/components/SpellcheckPanel.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"SpellcheckPanel.d.ts","sourceRoot":"","sources":["../../src/components/SpellcheckPanel.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrG,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAgBtF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,GAAG,EACH,gBAAgB,EAChB,gBAAgB,EAChB,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,KAAK,GAAG,KAAK,CAAC;IACnB,gBAAgB,EAAE,aAAa,EAAE,CAAC;IAClC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IACnE,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAqKA"}
|
|
@@ -5,6 +5,7 @@ import { IconChevronDown, IconX } from '@tabler/icons-react';
|
|
|
5
5
|
import { $replaceMatch, $selectMatch } from '../find/findReplaceActions.js';
|
|
6
6
|
import { $collectMisspellings } from '../spellcheck/spellcheckActions.js';
|
|
7
7
|
import { getSpeller, hasSpellDictionary } from '../spellcheck/spellDictionaries.js';
|
|
8
|
+
import { addUserWord, ignoreWord } from '../spellcheck/userWords.js';
|
|
8
9
|
const LANGUAGES = [
|
|
9
10
|
{ value: 'en', label: 'English' },
|
|
10
11
|
{ value: 'ur', label: 'اردو' },
|
|
@@ -17,7 +18,7 @@ const SUGGESTIONS_PER_WORD = 3;
|
|
|
17
18
|
* can replace it. Checking reads the document on demand, so it never flags
|
|
18
19
|
* words while the user is still typing.
|
|
19
20
|
*/
|
|
20
|
-
export function SpellcheckPanel({ strings, dir, onClose }) {
|
|
21
|
+
export function SpellcheckPanel({ strings, dir, dictionaryStores, onAddAutoCorrect, onClose, }) {
|
|
21
22
|
const [editor] = useLexicalComposerContext();
|
|
22
23
|
const t = strings.spellcheck;
|
|
23
24
|
const [language, setLanguage] = useState('en');
|
|
@@ -77,5 +78,14 @@ export function SpellcheckPanel({ strings, dir, onClose }) {
|
|
|
77
78
|
return (_jsxs("div", { className: "likhari-spell-panel", role: "region", "aria-label": t.title, dir: dir, children: [_jsxs("div", { className: "likhari-spell-header", children: [_jsx("strong", { children: t.title }), _jsx("button", { type: "button", className: "likhari-spell-icon", onClick: onClose, "aria-label": t.close, title: t.close, children: _jsx(IconX, { size: 14, stroke: 1.75 }) })] }), _jsxs("div", { className: "likhari-spell-controls", children: [_jsx("select", { "aria-label": t.language, value: language, onChange: (e) => {
|
|
78
79
|
setLanguage(e.target.value);
|
|
79
80
|
setItems(null);
|
|
80
|
-
}, children: LANGUAGES.filter((l) => hasSpellDictionary(l.value)).map((l) => (_jsx("option", { value: l.value, children: l.label }, l.value))) }), _jsx("button", { type: "button", onClick: check, disabled: checking, children: t.check })] }), items !== null && (_jsxs("div", { className: "likhari-spell-nav", children: [_jsx("span", { className: "likhari-spell-status", "aria-live": "polite", children: status }), _jsx("button", { type: "button", className: "likhari-spell-icon", onClick: () => step(-1), disabled: items.length === 0, "aria-label": t.previous, title: t.previous, children: _jsx(IconChevronDown, { size: 14, stroke: 1.75, style: { transform: 'rotate(180deg)' } }) }), _jsx("button", { type: "button", className: "likhari-spell-icon", onClick: () => step(1), disabled: items.length === 0, "aria-label": t.next, title: t.next, children: _jsx(IconChevronDown, { size: 14, stroke: 1.75 }) })] })), current && (_jsxs("div", { className: "likhari-spell-item", children: [_jsx("span", { className: "likhari-spell-word", children: current.word }),
|
|
81
|
+
}, children: LANGUAGES.filter((l) => hasSpellDictionary(l.value)).map((l) => (_jsx("option", { value: l.value, children: l.label }, l.value))) }), _jsx("button", { type: "button", onClick: check, disabled: checking, children: t.check })] }), items !== null && (_jsxs("div", { className: "likhari-spell-nav", children: [_jsx("span", { className: "likhari-spell-status", "aria-live": "polite", children: status }), _jsx("button", { type: "button", className: "likhari-spell-icon", onClick: () => step(-1), disabled: items.length === 0, "aria-label": t.previous, title: t.previous, children: _jsx(IconChevronDown, { size: 14, stroke: 1.75, style: { transform: 'rotate(180deg)' } }) }), _jsx("button", { type: "button", className: "likhari-spell-icon", onClick: () => step(1), disabled: items.length === 0, "aria-label": t.next, title: t.next, children: _jsx(IconChevronDown, { size: 14, stroke: 1.75 }) })] })), current && (_jsxs("div", { className: "likhari-spell-item", children: [_jsx("span", { className: "likhari-spell-word", children: current.word }), _jsxs("div", { className: "likhari-spell-suggestions", children: [_jsx("button", { type: "button", className: "likhari-spell-suggestion", onClick: () => {
|
|
82
|
+
ignoreWord(language, current.word);
|
|
83
|
+
void check();
|
|
84
|
+
}, children: strings.contextMenu.ignore }), _jsx("button", { type: "button", className: "likhari-spell-suggestion", onClick: () => {
|
|
85
|
+
void addUserWord(dictionaryStores, language, current.word).then((added) => {
|
|
86
|
+
if (!added)
|
|
87
|
+
ignoreWord(language, current.word);
|
|
88
|
+
void check();
|
|
89
|
+
});
|
|
90
|
+
}, children: strings.contextMenu.addToDictionary }), onAddAutoCorrect && (_jsx("button", { type: "button", className: "likhari-spell-suggestion", onClick: () => onAddAutoCorrect(current.word, language), children: strings.contextMenu.addToAutoCorrect }))] }), current.suggestions.length === 0 ? (_jsx("span", { className: "likhari-spell-note", children: t.noSuggestions })) : (_jsx("div", { className: "likhari-spell-suggestions", children: current.suggestions.map((s) => (_jsx("button", { type: "button", className: "likhari-spell-suggestion", onClick: () => replace(current, s), children: s }, s))) }))] }))] }));
|
|
81
91
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { type SpellLanguage } from '../spellcheck/spellDictionaries.js';
|
|
2
|
+
import { type UserWordStore } from '../spellcheck/userWords.js';
|
|
1
3
|
import { type DraftsToolbarOptions } from './DraftsDialog.js';
|
|
2
4
|
import type { ResolvedEditorFeatureConfig } from '@inshapardaz/likhari-core';
|
|
3
5
|
import { type FontOption } from '../fonts/index.js';
|
|
4
6
|
import type { Locale } from '../i18n/strings.js';
|
|
5
7
|
export interface ToolbarProps {
|
|
6
8
|
config: ResolvedEditorFeatureConfig;
|
|
9
|
+
/** Where words added to the dictionary are saved. */
|
|
10
|
+
dictionaryStores?: UserWordStore[];
|
|
11
|
+
/** Opens the auto-correct panel with the word filled in. */
|
|
12
|
+
onAddAutoCorrect?: (word: string, language: SpellLanguage) => void;
|
|
7
13
|
/** Whether the find-and-replace widget is open, and how to toggle it (owned by EditorRoot). */
|
|
8
14
|
findOpen?: boolean;
|
|
9
15
|
onToggleFind?: () => void;
|
|
@@ -25,5 +31,5 @@ export interface ToolbarProps {
|
|
|
25
31
|
/** Autosave drafts: when set, the toolbar offers a Drafts button that lists and restores them. */
|
|
26
32
|
drafts?: DraftsToolbarOptions;
|
|
27
33
|
}
|
|
28
|
-
export declare function Toolbar({ config, onSave, isDirty, showSave, findOpen, onToggleFind, spellOpen, onToggleSpell, autoCorrectOpen, onToggleAutoCorrect, fontOptions, direction, locale, drafts }: ToolbarProps): import("react").JSX.Element;
|
|
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;
|
|
29
35
|
//# sourceMappingURL=Toolbar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -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 { addUserWord, ignoreWord } from '../spellcheck/userWords.js';
|
|
14
15
|
import { $wordAtDomPoint, textPointAt } from '../spellcheck/spellingMenu.js';
|
|
15
16
|
import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode';
|
|
16
17
|
import { ImageDialog } from '../image/ImageDialog.js';
|
|
@@ -184,7 +185,7 @@ function PoetryMenuItems({ strings, layout, scale, centered, onAction, }) {
|
|
|
184
185
|
};
|
|
185
186
|
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')] }));
|
|
186
187
|
}
|
|
187
|
-
export function Toolbar({ config, onSave, isDirty, showSave, findOpen = false, onToggleFind, spellOpen = false, onToggleSpell, autoCorrectOpen = false, onToggleAutoCorrect, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
|
|
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 }) {
|
|
188
189
|
const [editor] = useLexicalComposerContext();
|
|
189
190
|
const [state, setState] = useState(INITIAL_STATE);
|
|
190
191
|
const strings = useStrings(locale);
|
|
@@ -521,11 +522,12 @@ export function Toolbar({ config, onSave, isDirty, showSave, findOpen = false, o
|
|
|
521
522
|
word = $wordAtDomPoint(point.node, point.offset);
|
|
522
523
|
}, { discrete: true });
|
|
523
524
|
if (word) {
|
|
524
|
-
const
|
|
525
|
+
const language = word.direction === 'rtl' ? 'ur' : 'en';
|
|
526
|
+
const speller = getSpeller(language);
|
|
525
527
|
if (speller) {
|
|
526
528
|
const checked = await speller;
|
|
527
529
|
if (!checked.correct(word.word)) {
|
|
528
|
-
menu.spelling = { suggestions: checked.suggest(word.word).slice(0, 5), match: word.match };
|
|
530
|
+
menu.spelling = { word: word.word, language, suggestions: checked.suggest(word.word).slice(0, 5), match: word.match };
|
|
529
531
|
}
|
|
530
532
|
}
|
|
531
533
|
}
|
|
@@ -829,7 +831,21 @@ export function Toolbar({ config, onSave, isDirty, showSave, findOpen = false, o
|
|
|
829
831
|
const match = contextMenu.spelling.match;
|
|
830
832
|
editor.update(() => $replaceMatch(match, suggestion), { discrete: true });
|
|
831
833
|
editor.focus();
|
|
832
|
-
}, children: suggestion }, suggestion))), _jsx(Menu.
|
|
834
|
+
}, children: suggestion }, suggestion))), _jsx(Menu.Item, { onClick: () => {
|
|
835
|
+
ignoreWord(contextMenu.spelling.language, contextMenu.spelling.word);
|
|
836
|
+
setContextMenu(null);
|
|
837
|
+
}, children: strings.contextMenu.ignore }), _jsx(Menu.Item, { onClick: () => {
|
|
838
|
+
const { language, word } = contextMenu.spelling;
|
|
839
|
+
// Without a writable store, the word is ignored for this session instead.
|
|
840
|
+
void addUserWord(dictionaryStores, language, word).then((added) => {
|
|
841
|
+
if (!added)
|
|
842
|
+
ignoreWord(language, word);
|
|
843
|
+
});
|
|
844
|
+
setContextMenu(null);
|
|
845
|
+
}, children: strings.contextMenu.addToDictionary }), onAddAutoCorrect && config.language.autocorrect && (_jsx(Menu.Item, { onClick: () => {
|
|
846
|
+
onAddAutoCorrect(contextMenu.spelling.word, contextMenu.spelling.language);
|
|
847
|
+
setContextMenu(null);
|
|
848
|
+
}, children: strings.contextMenu.addToAutoCorrect })), _jsx(Menu.Divider, {})] })), contextMenu?.link !== undefined && (_jsxs(_Fragment, { children: [_jsx(LinkMenuItems, { url: contextMenu.link, onEdit: () => {
|
|
833
849
|
restoreSelection();
|
|
834
850
|
openLinkDialog();
|
|
835
851
|
}, 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
|
@@ -198,9 +198,15 @@ export interface Strings {
|
|
|
198
198
|
copy: string;
|
|
199
199
|
paste: string;
|
|
200
200
|
selectAll: string;
|
|
201
|
+
ignore: string;
|
|
202
|
+
addToDictionary: string;
|
|
203
|
+
addToAutoCorrect: string;
|
|
201
204
|
spelling: string;
|
|
202
205
|
noSuggestions: string;
|
|
203
206
|
};
|
|
207
|
+
autoComplete: {
|
|
208
|
+
label: string;
|
|
209
|
+
};
|
|
204
210
|
link: {
|
|
205
211
|
menuLabel: string;
|
|
206
212
|
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
|
@@ -181,9 +181,15 @@ const en = {
|
|
|
181
181
|
copy: 'Copy',
|
|
182
182
|
paste: 'Paste',
|
|
183
183
|
selectAll: 'Select all',
|
|
184
|
+
ignore: 'Ignore',
|
|
185
|
+
addToDictionary: 'Add to dictionary',
|
|
186
|
+
addToAutoCorrect: 'Add to auto-correct',
|
|
184
187
|
spelling: 'Spelling suggestions',
|
|
185
188
|
noSuggestions: 'No suggestions',
|
|
186
189
|
},
|
|
190
|
+
autoComplete: {
|
|
191
|
+
label: 'Suggestions',
|
|
192
|
+
},
|
|
187
193
|
link: {
|
|
188
194
|
menuLabel: 'Link',
|
|
189
195
|
noUrl: '(no URL)',
|
|
@@ -446,9 +452,15 @@ const ur = {
|
|
|
446
452
|
copy: 'کاپی کریں',
|
|
447
453
|
paste: 'چسپاں کریں',
|
|
448
454
|
selectAll: 'سب منتخب کریں',
|
|
455
|
+
ignore: 'نظرانداز کریں',
|
|
456
|
+
addToDictionary: 'لغت میں شامل کریں',
|
|
457
|
+
addToAutoCorrect: 'خودکار تصحیح میں شامل کریں',
|
|
449
458
|
spelling: 'ہجے کی تجاویز',
|
|
450
459
|
noSuggestions: 'کوئی تجویز نہیں',
|
|
451
460
|
},
|
|
461
|
+
autoComplete: {
|
|
462
|
+
label: 'تجاویز',
|
|
463
|
+
},
|
|
452
464
|
link: {
|
|
453
465
|
menuLabel: 'لنک',
|
|
454
466
|
noUrl: '(کوئی یو آر ایل نہیں)',
|
|
@@ -711,9 +723,15 @@ const paShahmukhi = {
|
|
|
711
723
|
copy: 'کاپی کرو',
|
|
712
724
|
paste: 'چسپاں کرو',
|
|
713
725
|
selectAll: 'سارا چنو',
|
|
726
|
+
ignore: 'نظر انداز کرو',
|
|
727
|
+
addToDictionary: 'شبدکوش وچ پاؤ',
|
|
728
|
+
addToAutoCorrect: 'خودکار درستی وچ پاؤ',
|
|
714
729
|
spelling: 'ہجے دیاں تجویزاں',
|
|
715
730
|
noSuggestions: 'کوئی تجویز نئیں',
|
|
716
731
|
},
|
|
732
|
+
autoComplete: {
|
|
733
|
+
label: 'تجویزاں',
|
|
734
|
+
},
|
|
717
735
|
link: {
|
|
718
736
|
menuLabel: 'لنک',
|
|
719
737
|
noUrl: '(کوئی یو آر ایل نئیں)',
|
package/dist/index.d.ts
CHANGED
|
@@ -14,4 +14,6 @@ export type { AutoCorrectStore, AutoCorrectEntry } from './autocorrect/autoCorre
|
|
|
14
14
|
export { normalizeUrdu, normalizeUrduCharacters, removeUrduDiacritics, replaceUrduDigits } from './normalization/urduNormalize.js';
|
|
15
15
|
export type { UrduNormalizationOptions } from './normalization/urduNormalize.js';
|
|
16
16
|
export type { PunctuationOptions, PunctuationRule } from './autocorrect/punctuationRules.js';
|
|
17
|
+
export { localStorageUserWordStore, apiUserWordStore, ignoreWord, addUserWord, onSpellWordsChange } from './spellcheck/userWords.js';
|
|
18
|
+
export type { UserWordStore } from './spellcheck/userWords.js';
|
|
17
19
|
//# 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"}
|
|
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"}
|
package/dist/index.js
CHANGED
|
@@ -6,3 +6,4 @@ export { STRINGS, getStrings, useStrings, useUiStrings, UiStringsContext } from
|
|
|
6
6
|
export { registerSpellDictionary, hasSpellDictionary } from './spellcheck/spellDictionaries.js';
|
|
7
7
|
export { localStorageAutoCorrectStore, apiAutoCorrectStore, fileAutoCorrectStore } from './autocorrect/autoCorrectStores.js';
|
|
8
8
|
export { normalizeUrdu, normalizeUrduCharacters, removeUrduDiacritics, replaceUrduDigits } from './normalization/urduNormalize.js';
|
|
9
|
+
export { localStorageUserWordStore, apiUserWordStore, ignoreWord, addUserWord, onSpellWordsChange } from './spellcheck/userWords.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpellHighlightPlugin.d.ts","sourceRoot":"","sources":["../../src/spellcheck/SpellHighlightPlugin.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SpellHighlightPlugin.d.ts","sourceRoot":"","sources":["../../src/spellcheck/SpellHighlightPlugin.tsx"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,wBAAgB,oBAAoB,SAkDnC"}
|
|
@@ -2,6 +2,7 @@ import { useEffect } from 'react';
|
|
|
2
2
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
3
|
import { $collectMisspellings } from './spellcheckActions.js';
|
|
4
4
|
import { getSpeller } from './spellDictionaries.js';
|
|
5
|
+
import { onSpellWordsChange } from './userWords.js';
|
|
5
6
|
/** The CSS Custom Highlight name; editor.css styles it as a red wavy underline. */
|
|
6
7
|
const HIGHLIGHT = 'likhari-spelling';
|
|
7
8
|
const DEBOUNCE_MS = 300;
|
|
@@ -42,6 +43,7 @@ export function SpellHighlightPlugin() {
|
|
|
42
43
|
timer = setTimeout(paint, DEBOUNCE_MS);
|
|
43
44
|
};
|
|
44
45
|
const unregister = editor.registerUpdateListener(schedule);
|
|
46
|
+
const unsubscribe = onSpellWordsChange(schedule);
|
|
45
47
|
Promise.all([getSpeller('en'), getSpeller('ur')]).then(async ([en, ur]) => {
|
|
46
48
|
checkers = { ltr: en ? await en : undefined, rtl: ur ? await ur : undefined };
|
|
47
49
|
schedule();
|
|
@@ -50,6 +52,7 @@ export function SpellHighlightPlugin() {
|
|
|
50
52
|
active = false;
|
|
51
53
|
clearTimeout(timer);
|
|
52
54
|
unregister();
|
|
55
|
+
unsubscribe();
|
|
53
56
|
CSS.highlights.delete(HIGHLIGHT);
|
|
54
57
|
};
|
|
55
58
|
}, [editor]);
|
|
@@ -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":"
|
|
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,5 +1,7 @@
|
|
|
1
1
|
import nspell from 'nspell';
|
|
2
|
+
import { isAcceptedWord } from './userWords.js';
|
|
2
3
|
const loaders = new Map();
|
|
4
|
+
const loadedFiles = new Map();
|
|
3
5
|
const spellers = new Map();
|
|
4
6
|
async function fetchText(url) {
|
|
5
7
|
const response = await fetch(url);
|
|
@@ -22,6 +24,7 @@ loaders.set('en', englishLoader);
|
|
|
22
24
|
export function registerSpellDictionary(language, files) {
|
|
23
25
|
loaders.set(language, typeof files === 'function' ? files : async () => files);
|
|
24
26
|
spellers.delete(language);
|
|
27
|
+
loadedFiles.delete(language);
|
|
25
28
|
}
|
|
26
29
|
/** Whether a dictionary is registered for the language. */
|
|
27
30
|
export function hasSpellDictionary(language) {
|
|
@@ -33,16 +36,46 @@ export function hasSpellDictionary(language) {
|
|
|
33
36
|
* can skip spellchecking that language rather than flag every word.
|
|
34
37
|
*/
|
|
35
38
|
export function getSpeller(language) {
|
|
36
|
-
|
|
37
|
-
if (!loader)
|
|
39
|
+
if (!loaders.has(language))
|
|
38
40
|
return null;
|
|
39
41
|
let speller = spellers.get(language);
|
|
40
42
|
if (!speller) {
|
|
41
|
-
speller =
|
|
43
|
+
speller = filesFor(language)
|
|
44
|
+
.then(({ aff, dic }) => nspell(aff, dic))
|
|
45
|
+
.then((base) => ({
|
|
46
|
+
// Words the user added or ignored count as correct, whatever the dictionary says.
|
|
47
|
+
correct: (word) => isAcceptedWord(language, word) || base.correct(word),
|
|
48
|
+
suggest: (word) => base.suggest(word),
|
|
49
|
+
}));
|
|
42
50
|
spellers.set(language, speller);
|
|
43
51
|
}
|
|
44
52
|
return speller;
|
|
45
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
|
+
}
|
|
46
79
|
/**
|
|
47
80
|
* Letters and combining marks, with apostrophes inside a word kept. The
|
|
48
81
|
* Unicode classes cover Arabic-script letters and their diacritics, so Urdu
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SpellLanguage } from './spellDictionaries.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where words the user has added to the dictionary are kept. Like the
|
|
4
|
+
* auto-correct stores: the host can pass several, and words are added to the
|
|
5
|
+
* first one that accepts writes.
|
|
6
|
+
*/
|
|
7
|
+
export interface UserWordStore {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly readOnly?: boolean;
|
|
10
|
+
load(language: SpellLanguage): Promise<string[]>;
|
|
11
|
+
append?(language: SpellLanguage, word: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/** Words added to the dictionary, kept in this browser's localStorage. */
|
|
14
|
+
export declare function localStorageUserWordStore(options?: {
|
|
15
|
+
key?: string;
|
|
16
|
+
}): UserWordStore;
|
|
17
|
+
/**
|
|
18
|
+
* Words served by an API. `load` calls `GET {url}?language=xx` and expects a JSON
|
|
19
|
+
* array of strings; `append` calls `POST {url}` with `{ language, word }`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function apiUserWordStore(options: {
|
|
22
|
+
id?: string;
|
|
23
|
+
url: string;
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
fetch?: typeof fetch;
|
|
26
|
+
}): UserWordStore;
|
|
27
|
+
/** The words the user has added to the dictionary for a language. */
|
|
28
|
+
export declare function acceptedWords(language: SpellLanguage): string[];
|
|
29
|
+
/** Calls `listener` whenever accepted or ignored words change. Returns an unsubscribe function. */
|
|
30
|
+
export declare function onSpellWordsChange(listener: () => void): () => void;
|
|
31
|
+
/** Whether the speller should treat the word as correct: it is in the user's dictionary or ignored this session. */
|
|
32
|
+
export declare function isAcceptedWord(language: SpellLanguage, word: string): boolean;
|
|
33
|
+
/** Loads the user's words for each language from the stores (in order). */
|
|
34
|
+
export declare function loadUserWords(stores: UserWordStore[]): Promise<void>;
|
|
35
|
+
/** Adds a word to the first store that accepts writes. Returns false if none does. */
|
|
36
|
+
export declare function addUserWord(stores: UserWordStore[], language: SpellLanguage, word: string): Promise<boolean>;
|
|
37
|
+
/** Loads the session's ignored words. Call once at startup. */
|
|
38
|
+
export declare function restoreIgnoredWords(): void;
|
|
39
|
+
/** Ignores a word for the rest of this browser session (kept in sessionStorage). */
|
|
40
|
+
export declare function ignoreWord(language: SpellLanguage, word: string): void;
|
|
41
|
+
//# sourceMappingURL=userWords.d.ts.map
|
|
@@ -0,0 +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,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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const LOCAL_KEY = 'likhari-user-words';
|
|
2
|
+
const SESSION_KEY = 'likhari-spell-ignore';
|
|
3
|
+
const LANGUAGES = ['en', 'ur', 'pa-shahmukhi'];
|
|
4
|
+
function readStored(key) {
|
|
5
|
+
try {
|
|
6
|
+
const parsed = JSON.parse(localStorage.getItem(key) ?? '[]');
|
|
7
|
+
return Array.isArray(parsed) ? parsed.filter((e) => typeof e?.word === 'string') : [];
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Words added to the dictionary, kept in this browser's localStorage. */
|
|
14
|
+
export function localStorageUserWordStore(options = {}) {
|
|
15
|
+
const key = options.key ?? LOCAL_KEY;
|
|
16
|
+
return {
|
|
17
|
+
id: 'local-storage',
|
|
18
|
+
async load(language) {
|
|
19
|
+
return readStored(key)
|
|
20
|
+
.filter((e) => e.language === language)
|
|
21
|
+
.map((e) => e.word);
|
|
22
|
+
},
|
|
23
|
+
async append(language, word) {
|
|
24
|
+
const all = readStored(key);
|
|
25
|
+
if (all.some((e) => e.language === language && e.word === word))
|
|
26
|
+
return;
|
|
27
|
+
all.push({ language, word });
|
|
28
|
+
try {
|
|
29
|
+
localStorage.setItem(key, JSON.stringify(all));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Storage full or disabled: the word stays for this session only.
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Words served by an API. `load` calls `GET {url}?language=xx` and expects a JSON
|
|
39
|
+
* array of strings; `append` calls `POST {url}` with `{ language, word }`.
|
|
40
|
+
*/
|
|
41
|
+
export function apiUserWordStore(options) {
|
|
42
|
+
const doFetch = options.fetch ?? fetch;
|
|
43
|
+
return {
|
|
44
|
+
id: options.id ?? 'api',
|
|
45
|
+
async load(language) {
|
|
46
|
+
const response = await doFetch(`${options.url}?language=${encodeURIComponent(language)}`, { headers: options.headers });
|
|
47
|
+
if (!response.ok)
|
|
48
|
+
throw new Error(`User word API failed: ${response.status}`);
|
|
49
|
+
const data = await response.json();
|
|
50
|
+
return Array.isArray(data) ? data.filter((w) => typeof w === 'string') : [];
|
|
51
|
+
},
|
|
52
|
+
async append(language, word) {
|
|
53
|
+
const response = await doFetch(options.url, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
56
|
+
body: JSON.stringify({ language, word }),
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok)
|
|
59
|
+
throw new Error(`User word API rejected the word: ${response.status}`);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// Words the user accepted (kept in their store), and words ignored for this session.
|
|
64
|
+
const accepted = new Map(LANGUAGES.map((l) => [l, new Set()]));
|
|
65
|
+
const ignored = new Map(LANGUAGES.map((l) => [l, new Set()]));
|
|
66
|
+
const listeners = new Set();
|
|
67
|
+
function notify() {
|
|
68
|
+
for (const listener of listeners)
|
|
69
|
+
listener();
|
|
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
|
+
}
|
|
75
|
+
/** Calls `listener` whenever accepted or ignored words change. Returns an unsubscribe function. */
|
|
76
|
+
export function onSpellWordsChange(listener) {
|
|
77
|
+
listeners.add(listener);
|
|
78
|
+
return () => listeners.delete(listener);
|
|
79
|
+
}
|
|
80
|
+
/** Whether the speller should treat the word as correct: it is in the user's dictionary or ignored this session. */
|
|
81
|
+
export function isAcceptedWord(language, word) {
|
|
82
|
+
return accepted.get(language)?.has(word) === true || ignored.get(language)?.has(word) === true;
|
|
83
|
+
}
|
|
84
|
+
/** Loads the user's words for each language from the stores (in order). */
|
|
85
|
+
export async function loadUserWords(stores) {
|
|
86
|
+
for (const language of LANGUAGES) {
|
|
87
|
+
const set = accepted.get(language);
|
|
88
|
+
set.clear();
|
|
89
|
+
const results = await Promise.allSettled(stores.map((store) => store.load(language)));
|
|
90
|
+
for (const result of results)
|
|
91
|
+
if (result.status === 'fulfilled')
|
|
92
|
+
for (const word of result.value)
|
|
93
|
+
set.add(word);
|
|
94
|
+
}
|
|
95
|
+
notify();
|
|
96
|
+
}
|
|
97
|
+
/** Adds a word to the first store that accepts writes. Returns false if none does. */
|
|
98
|
+
export async function addUserWord(stores, language, word) {
|
|
99
|
+
const writable = stores.find((store) => !store.readOnly && store.append);
|
|
100
|
+
if (!writable?.append)
|
|
101
|
+
return false;
|
|
102
|
+
await writable.append(language, word);
|
|
103
|
+
accepted.get(language)?.add(word);
|
|
104
|
+
notify();
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
function readIgnored() {
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(sessionStorage.getItem(SESSION_KEY) ?? '[]');
|
|
110
|
+
return Array.isArray(parsed) ? parsed.filter((e) => typeof e?.word === 'string') : [];
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Loads the session's ignored words. Call once at startup. */
|
|
117
|
+
export function restoreIgnoredWords() {
|
|
118
|
+
for (const { language, word } of readIgnored())
|
|
119
|
+
ignored.get(language)?.add(word);
|
|
120
|
+
notify();
|
|
121
|
+
}
|
|
122
|
+
/** Ignores a word for the rest of this browser session (kept in sessionStorage). */
|
|
123
|
+
export function ignoreWord(language, word) {
|
|
124
|
+
ignored.get(language)?.add(word);
|
|
125
|
+
const all = readIgnored();
|
|
126
|
+
if (!all.some((e) => e.language === language && e.word === word))
|
|
127
|
+
all.push({ language, word });
|
|
128
|
+
try {
|
|
129
|
+
sessionStorage.setItem(SESSION_KEY, JSON.stringify(all));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Session storage unavailable: the word is ignored until the page reloads.
|
|
133
|
+
}
|
|
134
|
+
notify();
|
|
135
|
+
}
|
|
136
|
+
restoreIgnoredWords();
|
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
|
+
|