@inshapardaz/likhari-react 0.1.21-dev.208 → 0.1.21-dev.210
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 +10 -3
- package/dist/components/SpellcheckPanel.d.ts +3 -1
- package/dist/components/SpellcheckPanel.d.ts.map +1 -1
- package/dist/components/SpellcheckPanel.js +12 -2
- package/dist/components/Toolbar.d.ts +4 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +123 -108
- package/dist/i18n/strings.d.ts +10 -0
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +30 -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.map +1 -1
- package/dist/spellcheck/spellDictionaries.js +8 -1
- package/dist/spellcheck/spellingMenu.d.ts +15 -0
- package/dist/spellcheck/spellingMenu.d.ts.map +1 -0
- package/dist/spellcheck/spellingMenu.js +27 -0
- package/dist/spellcheck/userWords.d.ts +39 -0
- package/dist/spellcheck/userWords.d.ts.map +1 -0
- package/dist/spellcheck/userWords.js +132 -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":"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;AAGzE,OAAO,EAA4C,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAYtG,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;AASD,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,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,uGAkarB,CAAC"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -32,6 +32,7 @@ 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 { loadUserWords, localStorageUserWordStore } from './spellcheck/userWords.js';
|
|
35
36
|
import { LeaveDialog } from './components/LeaveDialog.js';
|
|
36
37
|
import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
|
|
37
38
|
import { ImageOptionsContext } from './image/ImageOptionsContext.js';
|
|
@@ -42,13 +43,15 @@ const DEFAULT_AUTOSAVE_MAX_BYTES = 2000000;
|
|
|
42
43
|
const DEFAULT_AUTOSAVE_MAX_DRAFTS = 20;
|
|
43
44
|
/** Used when the host passes no stores: corrections are kept in this browser. */
|
|
44
45
|
const DEFAULT_AUTOCORRECT_STORES = [localStorageAutoCorrectStore()];
|
|
46
|
+
/** Used when the host passes no dictionary stores: added words are kept in this browser. */
|
|
47
|
+
const DEFAULT_DICTIONARY_STORES = [localStorageUserWordStore()];
|
|
45
48
|
function initialEditorStateJson(initialContent) {
|
|
46
49
|
if (!initialContent)
|
|
47
50
|
return undefined;
|
|
48
51
|
const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
|
|
49
52
|
return JSON.stringify(state);
|
|
50
53
|
}
|
|
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) {
|
|
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) {
|
|
52
55
|
const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
|
|
53
56
|
const strings = useMemo(() => getStrings(locale), [locale]);
|
|
54
57
|
const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
|
|
@@ -78,6 +81,10 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorre
|
|
|
78
81
|
const [openPanel, setOpenPanel] = useState(null);
|
|
79
82
|
const [autoCorrectVersion, setAutoCorrectVersion] = useState(0);
|
|
80
83
|
const stores = autoCorrectStores ?? DEFAULT_AUTOCORRECT_STORES;
|
|
84
|
+
const wordStores = dictionaryStores ?? DEFAULT_DICTIONARY_STORES;
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
void loadUserWords(wordStores);
|
|
87
|
+
}, [wordStores]);
|
|
81
88
|
const togglePanel = (panel) => setOpenPanel((current) => (current === panel ? null : panel));
|
|
82
89
|
const isDirtyRef = useRef(isDirty);
|
|
83
90
|
isDirtyRef.current = isDirty;
|
|
@@ -291,9 +298,9 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorre
|
|
|
291
298
|
// rather than colliding on a shared one.
|
|
292
299
|
const portalTargetId = useId();
|
|
293
300
|
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
|
|
301
|
+
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, 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
302
|
? { 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: () => {
|
|
303
|
+
: 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, 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: () => {
|
|
297
304
|
handleSave();
|
|
298
305
|
settleLeave(true);
|
|
299
306
|
}, onSaveDraft: () => {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { Strings } from '../i18n/strings.js';
|
|
2
|
+
import { type UserWordStore } from '../spellcheck/userWords.js';
|
|
2
3
|
/**
|
|
3
4
|
* Steps through the misspelled words one at a time, like the find widget.
|
|
4
5
|
* Each word is selected in the editor as it comes up, and its suggestions
|
|
5
6
|
* can replace it. Checking reads the document on demand, so it never flags
|
|
6
7
|
* words while the user is still typing.
|
|
7
8
|
*/
|
|
8
|
-
export declare function SpellcheckPanel({ strings, dir, onClose }: {
|
|
9
|
+
export declare function SpellcheckPanel({ strings, dir, dictionaryStores, onClose, }: {
|
|
9
10
|
strings: Strings;
|
|
10
11
|
dir: 'ltr' | 'rtl';
|
|
12
|
+
dictionaryStores: UserWordStore[];
|
|
11
13
|
onClose: () => void;
|
|
12
14
|
}): import("react").JSX.Element;
|
|
13
15
|
//# 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;AAI/C,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAgBtF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,GAAG,EACH,gBAAgB,EAChB,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,KAAK,GAAG,KAAK,CAAC;IACnB,gBAAgB,EAAE,aAAa,EAAE,CAAC;IAClC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAgKA"}
|
|
@@ -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, 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 })] }), 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,12 @@
|
|
|
1
|
+
import { type UserWordStore } from '../spellcheck/userWords.js';
|
|
1
2
|
import { type DraftsToolbarOptions } from './DraftsDialog.js';
|
|
2
3
|
import type { ResolvedEditorFeatureConfig } from '@inshapardaz/likhari-core';
|
|
3
4
|
import { type FontOption } from '../fonts/index.js';
|
|
4
5
|
import type { Locale } from '../i18n/strings.js';
|
|
5
6
|
export interface ToolbarProps {
|
|
6
7
|
config: ResolvedEditorFeatureConfig;
|
|
8
|
+
/** Where words added to the dictionary are saved. */
|
|
9
|
+
dictionaryStores?: UserWordStore[];
|
|
7
10
|
/** Whether the find-and-replace widget is open, and how to toggle it (owned by EditorRoot). */
|
|
8
11
|
findOpen?: boolean;
|
|
9
12
|
onToggleFind?: () => void;
|
|
@@ -25,5 +28,5 @@ export interface ToolbarProps {
|
|
|
25
28
|
/** Autosave drafts: when set, the toolbar offers a Drafts button that lists and restores them. */
|
|
26
29
|
drafts?: DraftsToolbarOptions;
|
|
27
30
|
}
|
|
28
|
-
export declare function Toolbar({ config, onSave, isDirty, showSave, findOpen, onToggleFind, spellOpen, onToggleSpell, autoCorrectOpen, onToggleAutoCorrect, fontOptions, direction, locale, drafts }: ToolbarProps): import("react").JSX.Element;
|
|
31
|
+
export declare function Toolbar({ config, dictionaryStores, onSave, isDirty, showSave, findOpen, onToggleFind, spellOpen, onToggleSpell, autoCorrectOpen, onToggleAutoCorrect, fontOptions, direction, locale, drafts }: ToolbarProps): import("react").JSX.Element;
|
|
29
32
|
//# 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":"AA2CA,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,+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,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,+BA2mC/R"}
|
|
@@ -2,13 +2,17 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { Menu, Select } from '@mantine/core';
|
|
4
4
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
5
|
-
import { $getSelection, $isRangeSelection, $isElementNode, $setSelection, $getRoot, $getNearestNodeFromDOMNode, $createTextNode, KEY_MODIFIER_COMMAND, COMMAND_PRIORITY_NORMAL, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND, UNDO_COMMAND, REDO_COMMAND, SELECTION_CHANGE_COMMAND, CAN_UNDO_COMMAND, CAN_REDO_COMMAND, COMMAND_PRIORITY_LOW, } from 'lexical';
|
|
5
|
+
import { $getSelection, $isRangeSelection, $isElementNode, $setSelection, $getRoot, $selectAll, $getNearestNodeFromDOMNode, $createTextNode, KEY_MODIFIER_COMMAND, COMMAND_PRIORITY_NORMAL, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND, UNDO_COMMAND, REDO_COMMAND, SELECTION_CHANGE_COMMAND, CAN_UNDO_COMMAND, CAN_REDO_COMMAND, COMMAND_PRIORITY_LOW, } from 'lexical';
|
|
6
6
|
import { $getSelectionStyleValueForProperty, $patchStyleText, $setBlocksType } from '@lexical/selection';
|
|
7
7
|
import { $createParagraphNode } from 'lexical';
|
|
8
8
|
import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode } from '@lexical/rich-text';
|
|
9
9
|
import { $isListNode, INSERT_CHECK_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, REMOVE_LIST_COMMAND, } from '@lexical/list';
|
|
10
10
|
import { $findMatchingParent, $insertNodeToNearestRoot } from '@lexical/utils';
|
|
11
11
|
import { CORRECT_DOCUMENT_COMMAND } from '../autocorrect/AutoCorrectPlugin.js';
|
|
12
|
+
import { $replaceMatch } from '../find/findReplaceActions.js';
|
|
13
|
+
import { getSpeller } from '../spellcheck/spellDictionaries.js';
|
|
14
|
+
import { addUserWord, ignoreWord } from '../spellcheck/userWords.js';
|
|
15
|
+
import { $wordAtDomPoint, textPointAt } from '../spellcheck/spellingMenu.js';
|
|
12
16
|
import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode';
|
|
13
17
|
import { ImageDialog } from '../image/ImageDialog.js';
|
|
14
18
|
import { $createImageNode } from '../image/ImageNode.js';
|
|
@@ -30,7 +34,7 @@ import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fo
|
|
|
30
34
|
import { setStyleProperty } from '../utils/style.js';
|
|
31
35
|
import { useStrings } from '../i18n/useStrings.js';
|
|
32
36
|
import { usePortalTarget } from '../PortalTargetContext.js';
|
|
33
|
-
import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPageBreak, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconArrowMergeBoth, IconArrowsSplit2, IconColumnInsertLeft, IconColumnInsertRight, IconColumnRemove, IconColumns, IconSearch, IconColumns1, IconColumns2, IconNumber1Small, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconArrowsMaximize, IconArrowsMinimize, IconArrowsShuffle, IconTable, IconTableMinus, IconTrash, IconTableOptions, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
|
|
37
|
+
import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPageBreak, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconArrowMergeBoth, IconArrowsSplit2, IconColumnInsertLeft, IconColumnInsertRight, IconColumnRemove, IconColumns, IconClipboard, IconCopy, IconCut, IconSearch, IconSelectAll, IconColumns1, IconColumns2, IconNumber1Small, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconArrowsMaximize, IconArrowsMinimize, IconArrowsShuffle, IconTable, IconTableMinus, IconTrash, IconTableOptions, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
|
|
34
38
|
/** Sentinel for "no inline style": Lexical returns the default it is given when
|
|
35
39
|
* the selection has none, so this tells "unstyled" (show the canvas default)
|
|
36
40
|
* apart from "styled with a value the dropdown doesn't list" (show nothing). */
|
|
@@ -140,8 +144,6 @@ function OverflowItem({ icon: Icon, label, active, onClick, }) {
|
|
|
140
144
|
// Keep the editor's selection: a mousedown here would blur the canvas.
|
|
141
145
|
onMouseDown: (e) => e.preventDefault(), onClick: onClick, children: label }));
|
|
142
146
|
}
|
|
143
|
-
/** The link menu's contents — shared by the toolbar button and the right-click
|
|
144
|
-
* menu: the URL (opens in a new tab), Edit link, Remove link. */
|
|
145
147
|
function LinkMenuItems({ url, onEdit, onRemove, strings }) {
|
|
146
148
|
// Only offer the link as clickable if it's a URL the editor would have accepted.
|
|
147
149
|
const safeUrl = normalizeLinkUrl(url);
|
|
@@ -183,7 +185,7 @@ function PoetryMenuItems({ strings, layout, scale, centered, onAction, }) {
|
|
|
183
185
|
};
|
|
184
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')] }));
|
|
185
187
|
}
|
|
186
|
-
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 = [], onSave, isDirty, showSave, findOpen = false, onToggleFind, spellOpen = false, onToggleSpell, autoCorrectOpen = false, onToggleAutoCorrect, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
|
|
187
189
|
const [editor] = useLexicalComposerContext();
|
|
188
190
|
const [state, setState] = useState(INITIAL_STATE);
|
|
189
191
|
const strings = useStrings(locale);
|
|
@@ -464,112 +466,116 @@ export function Toolbar({ config, onSave, isDirty, showSave, findOpen = false, o
|
|
|
464
466
|
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
|
|
465
467
|
closeLinkDialog();
|
|
466
468
|
};
|
|
467
|
-
//
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (!config.links)
|
|
471
|
-
return;
|
|
472
|
-
const onContextMenu = (event) => {
|
|
473
|
-
const anchor = event.target?.closest?.('a');
|
|
474
|
-
if (!anchor)
|
|
475
|
-
return;
|
|
476
|
-
let url = null;
|
|
477
|
-
// Select the whole link first, so Edit / Remove act on it wherever the caret was.
|
|
478
|
-
editor.update(() => {
|
|
479
|
-
const node = $getNearestNodeFromDOMNode(anchor);
|
|
480
|
-
const link = node ? $findMatchingParent(node, $isLinkNode) : null;
|
|
481
|
-
if (!link || !$isLinkNode(link))
|
|
482
|
-
return;
|
|
483
|
-
url = link.getURL();
|
|
484
|
-
link.select(0, link.getChildrenSize());
|
|
485
|
-
}, { discrete: true });
|
|
486
|
-
if (url === null)
|
|
487
|
-
return;
|
|
488
|
-
event.preventDefault();
|
|
489
|
-
snapshotSelectionRef.current();
|
|
490
|
-
setLinkContext({ x: event.clientX, y: event.clientY, url });
|
|
491
|
-
};
|
|
492
|
-
return editor.registerRootListener((root, previous) => {
|
|
493
|
-
previous?.removeEventListener('contextmenu', onContextMenu);
|
|
494
|
-
root?.addEventListener('contextmenu', onContextMenu);
|
|
495
|
-
});
|
|
496
|
-
}, [editor, config.links]);
|
|
497
|
-
// Table/poetry contextual-menu actions. The menu takes focus, so the
|
|
498
|
-
// selection is snapshotted when it opens and restored before an action runs.
|
|
469
|
+
// One right-click menu for the whole editor, replacing the browser's own. It
|
|
470
|
+
// adds what applies to what is under the pointer (a spelling fix, a link, a
|
|
471
|
+
// table cell, a poetry couplet) and always offers cut, copy, paste and select all.
|
|
499
472
|
const runMenuAction = (action) => () => {
|
|
500
473
|
restoreSelection();
|
|
501
474
|
editor.update(action);
|
|
502
475
|
editor.focus();
|
|
503
476
|
};
|
|
504
|
-
|
|
505
|
-
const [tableContext, setTableContext] = useState(null);
|
|
477
|
+
const [contextMenu, setContextMenu] = useState(null);
|
|
506
478
|
useEffect(() => {
|
|
507
|
-
|
|
508
|
-
return;
|
|
509
|
-
const onContextMenu = (event) => {
|
|
510
|
-
if (event.defaultPrevented)
|
|
511
|
-
return; // a link inside the cell already took it
|
|
512
|
-
const cellElement = event.target?.closest?.('td, th');
|
|
513
|
-
if (!cellElement)
|
|
514
|
-
return;
|
|
515
|
-
let handled = false;
|
|
516
|
-
editor.update(() => {
|
|
517
|
-
const node = $getNearestNodeFromDOMNode(cellElement);
|
|
518
|
-
const cell = node ? $getTableCellNodeFromLexicalNode(node) : null;
|
|
519
|
-
if (!cell)
|
|
520
|
-
return;
|
|
521
|
-
handled = true;
|
|
522
|
-
const selection = $getSelection();
|
|
523
|
-
// Keep a multi-cell selection that includes this cell, so "insert
|
|
524
|
-
// rows" / "delete columns" act on everything selected.
|
|
525
|
-
const insideSelection = $isTableSelection(selection) && selection.getNodes().some((n) => n.getKey() === cell.getKey());
|
|
526
|
-
if (!insideSelection)
|
|
527
|
-
cell.selectEnd();
|
|
528
|
-
}, { discrete: true });
|
|
529
|
-
if (!handled)
|
|
530
|
-
return;
|
|
479
|
+
const onContextMenu = async (event) => {
|
|
531
480
|
event.preventDefault();
|
|
532
481
|
snapshotSelectionRef.current();
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}, [editor, config.tables]);
|
|
541
|
-
// Right-clicking a poetry couplet opens the same options menu at the pointer.
|
|
542
|
-
const [poetryContext, setPoetryContext] = useState(null);
|
|
543
|
-
useEffect(() => {
|
|
544
|
-
if (!config.poetry.enabled)
|
|
545
|
-
return;
|
|
546
|
-
const onContextMenu = (event) => {
|
|
547
|
-
if (event.defaultPrevented)
|
|
548
|
-
return; // a link inside the couplet already took it
|
|
549
|
-
const coupletElement = event.target?.closest?.('.likhari-poetry');
|
|
550
|
-
if (!coupletElement)
|
|
551
|
-
return;
|
|
552
|
-
let handled = false;
|
|
482
|
+
const target = event.target;
|
|
483
|
+
const anchor = config.links ? (target?.closest?.('a') ?? null) : null;
|
|
484
|
+
const cell = config.tables ? (target?.closest?.('td, th') ?? null) : null;
|
|
485
|
+
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 menu = { x: event.clientX, y: event.clientY };
|
|
488
|
+
let word = null;
|
|
553
489
|
editor.update(() => {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
490
|
+
if (anchor) {
|
|
491
|
+
const node = $getNearestNodeFromDOMNode(anchor);
|
|
492
|
+
const link = node ? $findMatchingParent(node, $isLinkNode) : null;
|
|
493
|
+
if ($isLinkNode(link)) {
|
|
494
|
+
menu.link = link.getURL();
|
|
495
|
+
// Select the whole link first, so Edit / Remove act on it wherever the caret was.
|
|
496
|
+
link.select(0, link.getChildrenSize());
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (cell) {
|
|
500
|
+
const node = $getNearestNodeFromDOMNode(cell);
|
|
501
|
+
const tableCell = node ? $getTableCellNodeFromLexicalNode(node) : null;
|
|
502
|
+
if (tableCell) {
|
|
503
|
+
const selection = $getSelection();
|
|
504
|
+
// Keep a multi-cell selection that includes this cell.
|
|
505
|
+
const insideSelection = $isTableSelection(selection) && selection.getNodes().some((n) => n.getKey() === tableCell.getKey());
|
|
506
|
+
if (!insideSelection)
|
|
507
|
+
tableCell.selectEnd();
|
|
508
|
+
const size = $getTableSelectionSize();
|
|
509
|
+
menu.table = { rows: size.rows, columns: size.columns };
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (couplet) {
|
|
513
|
+
const node = $getNearestNodeFromDOMNode(couplet);
|
|
514
|
+
const block = node ? $findMatchingParent(node, $isPoetryBlockNode) : null;
|
|
515
|
+
if (block) {
|
|
516
|
+
menu.poetry = true;
|
|
517
|
+
if (!$getPoetryBlockFromSelection())
|
|
518
|
+
block.selectStart();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (point)
|
|
522
|
+
word = $wordAtDomPoint(point.node, point.offset);
|
|
561
523
|
}, { discrete: true });
|
|
562
|
-
if (
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
524
|
+
if (word) {
|
|
525
|
+
const language = word.direction === 'rtl' ? 'ur' : 'en';
|
|
526
|
+
const speller = getSpeller(language);
|
|
527
|
+
if (speller) {
|
|
528
|
+
const checked = await speller;
|
|
529
|
+
if (!checked.correct(word.word)) {
|
|
530
|
+
menu.spelling = { word: word.word, language, suggestions: checked.suggest(word.word).slice(0, 5), match: word.match };
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
setContextMenu(menu);
|
|
567
535
|
};
|
|
568
536
|
return editor.registerRootListener((root, previous) => {
|
|
569
537
|
previous?.removeEventListener('contextmenu', onContextMenu);
|
|
570
538
|
root?.addEventListener('contextmenu', onContextMenu);
|
|
571
539
|
});
|
|
572
|
-
}, [editor, config.poetry.enabled]);
|
|
540
|
+
}, [editor, config.links, config.tables, config.poetry.enabled, config.language.spellCheck]);
|
|
541
|
+
// Cut and copy put the selected text on the clipboard as plain text.
|
|
542
|
+
const copyOrCut = (cut) => async () => {
|
|
543
|
+
restoreSelection();
|
|
544
|
+
let text = '';
|
|
545
|
+
editor.update(() => {
|
|
546
|
+
const selection = $getSelection();
|
|
547
|
+
if (!$isRangeSelection(selection))
|
|
548
|
+
return;
|
|
549
|
+
text = selection.getTextContent();
|
|
550
|
+
if (cut)
|
|
551
|
+
selection.removeText();
|
|
552
|
+
}, { discrete: true });
|
|
553
|
+
if (text)
|
|
554
|
+
await navigator.clipboard?.writeText(text).catch(() => undefined);
|
|
555
|
+
editor.focus();
|
|
556
|
+
};
|
|
557
|
+
// Paste reads plain text from the clipboard (the browser may ask for permission).
|
|
558
|
+
const paste = async () => {
|
|
559
|
+
restoreSelection();
|
|
560
|
+
let text;
|
|
561
|
+
try {
|
|
562
|
+
text = await navigator.clipboard.readText();
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
editor.update(() => {
|
|
568
|
+
const selection = $getSelection();
|
|
569
|
+
if (!$isRangeSelection(selection))
|
|
570
|
+
return;
|
|
571
|
+
text.split(/\r?\n/).forEach((line, index) => {
|
|
572
|
+
if (index > 0)
|
|
573
|
+
selection.insertParagraph();
|
|
574
|
+
selection.insertText(line);
|
|
575
|
+
});
|
|
576
|
+
}, { discrete: true });
|
|
577
|
+
editor.focus();
|
|
578
|
+
};
|
|
573
579
|
// Ctrl/Cmd+K opens the link dialog.
|
|
574
580
|
useEffect(() => {
|
|
575
581
|
if (!config.links)
|
|
@@ -818,19 +824,28 @@ export function Toolbar({ config, onSave, isDirty, showSave, findOpen = false, o
|
|
|
818
824
|
.filter((group) => !overflowGroupKeys.has(group.key))
|
|
819
825
|
.map((group) => (_jsx(MovableGroupButtons, { group: group }, group.key))), overflowGroupKeys.size > 0 && (_jsxs(Menu, { position: "bottom-end", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 210, closeOnItemClick: true, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "aria-label": strings.toolbar.moreFormatting, title: strings.toolbar.moreFormatting, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconDots, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: movableGroups
|
|
820
826
|
.filter((group) => overflowGroupKeys.has(group.key))
|
|
821
|
-
.flatMap((group) => group.items.map((item) => (_jsx(OverflowItem, { icon: item.icon, label: item.label, active: item.active, onClick: runOverflowAction(item.onClick) }, item.key)))) })] })),
|
|
822
|
-
if (!opened)
|
|
823
|
-
setLinkContext(null);
|
|
824
|
-
}, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: linkContext?.x ?? -9999, top: linkContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(LinkMenuItems, { url: linkContext?.url ?? '', onEdit: () => {
|
|
825
|
-
restoreSelection();
|
|
826
|
-
openLinkDialog();
|
|
827
|
-
}, onRemove: removeLink, strings: strings }) })] })), config.tables && (_jsxs(Menu, { opened: tableContext !== null, onChange: (opened) => {
|
|
828
|
-
if (!opened)
|
|
829
|
-
setTableContext(null);
|
|
830
|
-
}, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: tableContext?.x ?? -9999, top: tableContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: tableContext?.rows ?? 1, columns: tableContext?.columns ?? 1, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }) })] })), config.poetry.enabled && (_jsxs(Menu, { opened: poetryContext !== null, onChange: (opened) => {
|
|
827
|
+
.flatMap((group) => group.items.map((item) => (_jsx(OverflowItem, { icon: item.icon, label: item.label, active: item.active, onClick: runOverflowAction(item.onClick) }, item.key)))) })] })), _jsxs(Menu, { opened: contextMenu !== null, onChange: (opened) => {
|
|
831
828
|
if (!opened)
|
|
832
|
-
|
|
833
|
-
}, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width:
|
|
829
|
+
setContextMenu(null);
|
|
830
|
+
}, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 260, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: contextMenu?.x ?? -9999, top: contextMenu?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsxs(Menu.Dropdown, { children: [contextMenu?.spelling && (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: strings.contextMenu.spelling }), contextMenu.spelling.suggestions.length === 0 && _jsx(Menu.Item, { disabled: true, children: strings.contextMenu.noSuggestions }), contextMenu.spelling.suggestions.map((suggestion) => (_jsx(Menu.Item, { onClick: () => {
|
|
831
|
+
const match = contextMenu.spelling.match;
|
|
832
|
+
editor.update(() => $replaceMatch(match, suggestion), { discrete: true });
|
|
833
|
+
editor.focus();
|
|
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 }), _jsx(Menu.Divider, {})] })), contextMenu?.link !== undefined && (_jsxs(_Fragment, { children: [_jsx(LinkMenuItems, { url: contextMenu.link, onEdit: () => {
|
|
846
|
+
restoreSelection();
|
|
847
|
+
openLinkDialog();
|
|
848
|
+
}, 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: {
|
|
834
849
|
position: 'absolute',
|
|
835
850
|
visibility: 'hidden',
|
|
836
851
|
pointerEvents: 'none',
|
package/dist/i18n/strings.d.ts
CHANGED
|
@@ -193,6 +193,16 @@ export interface Strings {
|
|
|
193
193
|
close: string;
|
|
194
194
|
correctDocument: string;
|
|
195
195
|
};
|
|
196
|
+
contextMenu: {
|
|
197
|
+
cut: string;
|
|
198
|
+
copy: string;
|
|
199
|
+
paste: string;
|
|
200
|
+
selectAll: string;
|
|
201
|
+
ignore: string;
|
|
202
|
+
addToDictionary: string;
|
|
203
|
+
spelling: string;
|
|
204
|
+
noSuggestions: string;
|
|
205
|
+
};
|
|
196
206
|
link: {
|
|
197
207
|
menuLabel: string;
|
|
198
208
|
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
|
@@ -176,6 +176,16 @@ const en = {
|
|
|
176
176
|
close: 'Close',
|
|
177
177
|
correctDocument: 'Correct whole document',
|
|
178
178
|
},
|
|
179
|
+
contextMenu: {
|
|
180
|
+
cut: 'Cut',
|
|
181
|
+
copy: 'Copy',
|
|
182
|
+
paste: 'Paste',
|
|
183
|
+
selectAll: 'Select all',
|
|
184
|
+
ignore: 'Ignore',
|
|
185
|
+
addToDictionary: 'Add to dictionary',
|
|
186
|
+
spelling: 'Spelling suggestions',
|
|
187
|
+
noSuggestions: 'No suggestions',
|
|
188
|
+
},
|
|
179
189
|
link: {
|
|
180
190
|
menuLabel: 'Link',
|
|
181
191
|
noUrl: '(no URL)',
|
|
@@ -433,6 +443,16 @@ const ur = {
|
|
|
433
443
|
close: 'بند کریں',
|
|
434
444
|
correctDocument: 'تمام متن کی تصحیح کریں',
|
|
435
445
|
},
|
|
446
|
+
contextMenu: {
|
|
447
|
+
cut: 'کاٹیں',
|
|
448
|
+
copy: 'کاپی کریں',
|
|
449
|
+
paste: 'چسپاں کریں',
|
|
450
|
+
selectAll: 'سب منتخب کریں',
|
|
451
|
+
ignore: 'نظرانداز کریں',
|
|
452
|
+
addToDictionary: 'لغت میں شامل کریں',
|
|
453
|
+
spelling: 'ہجے کی تجاویز',
|
|
454
|
+
noSuggestions: 'کوئی تجویز نہیں',
|
|
455
|
+
},
|
|
436
456
|
link: {
|
|
437
457
|
menuLabel: 'لنک',
|
|
438
458
|
noUrl: '(کوئی یو آر ایل نہیں)',
|
|
@@ -690,6 +710,16 @@ const paShahmukhi = {
|
|
|
690
710
|
close: 'بند کرو',
|
|
691
711
|
correctDocument: 'پورے متن دی درستی کرو',
|
|
692
712
|
},
|
|
713
|
+
contextMenu: {
|
|
714
|
+
cut: 'کٹو',
|
|
715
|
+
copy: 'کاپی کرو',
|
|
716
|
+
paste: 'چسپاں کرو',
|
|
717
|
+
selectAll: 'سارا چنو',
|
|
718
|
+
ignore: 'نظر انداز کرو',
|
|
719
|
+
addToDictionary: 'شبدکوش وچ پاؤ',
|
|
720
|
+
spelling: 'ہجے دیاں تجویزاں',
|
|
721
|
+
noSuggestions: 'کوئی تجویز نئیں',
|
|
722
|
+
},
|
|
693
723
|
link: {
|
|
694
724
|
menuLabel: 'لنک',
|
|
695
725
|
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]);
|
|
@@ -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;AAmB3C;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAGpG;AAED,2DAA2D;AAC3D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAe3E;AAED,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AASD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAMhD;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,EAAE,CAEzE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import nspell from 'nspell';
|
|
2
|
+
import { isAcceptedWord } from './userWords.js';
|
|
2
3
|
const loaders = new Map();
|
|
3
4
|
const spellers = new Map();
|
|
4
5
|
async function fetchText(url) {
|
|
@@ -38,7 +39,13 @@ export function getSpeller(language) {
|
|
|
38
39
|
return null;
|
|
39
40
|
let speller = spellers.get(language);
|
|
40
41
|
if (!speller) {
|
|
41
|
-
speller = loader()
|
|
42
|
+
speller = loader()
|
|
43
|
+
.then(({ aff, dic }) => nspell(aff, dic))
|
|
44
|
+
.then((base) => ({
|
|
45
|
+
// Words the user added or ignored count as correct, whatever the dictionary says.
|
|
46
|
+
correct: (word) => isAcceptedWord(language, word) || base.correct(word),
|
|
47
|
+
suggest: (word) => base.suggest(word),
|
|
48
|
+
}));
|
|
42
49
|
spellers.set(language, speller);
|
|
43
50
|
}
|
|
44
51
|
return speller;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FindMatch } from '../find/findReplaceActions.js';
|
|
2
|
+
/** A word under the pointer, as a Lexical range, with the direction of its block. */
|
|
3
|
+
export interface WordAtPoint {
|
|
4
|
+
word: string;
|
|
5
|
+
direction: 'ltr' | 'rtl';
|
|
6
|
+
match: FindMatch;
|
|
7
|
+
}
|
|
8
|
+
/** The DOM text node and offset under a viewport point, or null if the point is not on text. */
|
|
9
|
+
export declare function textPointAt(x: number, y: number): {
|
|
10
|
+
node: Node;
|
|
11
|
+
offset: number;
|
|
12
|
+
} | null;
|
|
13
|
+
/** The whole word around an offset in a Lexical text node. Must run inside an editor read or update. */
|
|
14
|
+
export declare function $wordAtDomPoint(domText: Node, offset: number): WordAtPoint | null;
|
|
15
|
+
//# sourceMappingURL=spellingMenu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spellingMenu.d.ts","sourceRoot":"","sources":["../../src/spellcheck/spellingMenu.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAG5D,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,gGAAgG;AAChG,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAUvF;AAED,wGAAwG;AACxG,wBAAgB,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAWjF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { $getNearestNodeFromDOMNode, $isTextNode } from 'lexical';
|
|
2
|
+
import { wordsIn } from './spellDictionaries.js';
|
|
3
|
+
/** The DOM text node and offset under a viewport point, or null if the point is not on text. */
|
|
4
|
+
export function textPointAt(x, y) {
|
|
5
|
+
const doc = document;
|
|
6
|
+
if (doc.caretPositionFromPoint) {
|
|
7
|
+
const position = doc.caretPositionFromPoint(x, y);
|
|
8
|
+
return position && position.offsetNode.nodeType === Node.TEXT_NODE ? { node: position.offsetNode, offset: position.offset } : null;
|
|
9
|
+
}
|
|
10
|
+
const range = doc.caretRangeFromPoint?.(x, y);
|
|
11
|
+
return range && range.startContainer.nodeType === Node.TEXT_NODE ? { node: range.startContainer, offset: range.startOffset } : null;
|
|
12
|
+
}
|
|
13
|
+
/** The whole word around an offset in a Lexical text node. Must run inside an editor read or update. */
|
|
14
|
+
export function $wordAtDomPoint(domText, offset) {
|
|
15
|
+
const node = $getNearestNodeFromDOMNode(domText);
|
|
16
|
+
if (!$isTextNode(node))
|
|
17
|
+
return null;
|
|
18
|
+
const text = node.getTextContent();
|
|
19
|
+
const span = wordsIn(text).find((s) => offset >= s.start && offset <= s.end);
|
|
20
|
+
if (!span)
|
|
21
|
+
return null;
|
|
22
|
+
return {
|
|
23
|
+
word: span.word,
|
|
24
|
+
direction: node.getParentOrThrow().getDirection() === 'rtl' ? 'rtl' : 'ltr',
|
|
25
|
+
match: { anchorKey: node.getKey(), anchorOffset: span.start, focusKey: node.getKey(), focusOffset: span.end },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
/** Calls `listener` whenever accepted or ignored words change. Returns an unsubscribe function. */
|
|
28
|
+
export declare function onSpellWordsChange(listener: () => void): () => void;
|
|
29
|
+
/** Whether the speller should treat the word as correct: it is in the user's dictionary or ignored this session. */
|
|
30
|
+
export declare function isAcceptedWord(language: SpellLanguage, word: string): boolean;
|
|
31
|
+
/** Loads the user's words for each language from the stores (in order). */
|
|
32
|
+
export declare function loadUserWords(stores: UserWordStore[]): Promise<void>;
|
|
33
|
+
/** Adds a word to the first store that accepts writes. Returns false if none does. */
|
|
34
|
+
export declare function addUserWord(stores: UserWordStore[], language: SpellLanguage, word: string): Promise<boolean>;
|
|
35
|
+
/** Loads the session's ignored words. Call once at startup. */
|
|
36
|
+
export declare function restoreIgnoredWords(): void;
|
|
37
|
+
/** Ignores a word for the rest of this browser session (kept in sessionStorage). */
|
|
38
|
+
export declare function ignoreWord(language: SpellLanguage, word: string): void;
|
|
39
|
+
//# 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,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,132 @@
|
|
|
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
|
+
/** Calls `listener` whenever accepted or ignored words change. Returns an unsubscribe function. */
|
|
72
|
+
export function onSpellWordsChange(listener) {
|
|
73
|
+
listeners.add(listener);
|
|
74
|
+
return () => listeners.delete(listener);
|
|
75
|
+
}
|
|
76
|
+
/** Whether the speller should treat the word as correct: it is in the user's dictionary or ignored this session. */
|
|
77
|
+
export function isAcceptedWord(language, word) {
|
|
78
|
+
return accepted.get(language)?.has(word) === true || ignored.get(language)?.has(word) === true;
|
|
79
|
+
}
|
|
80
|
+
/** Loads the user's words for each language from the stores (in order). */
|
|
81
|
+
export async function loadUserWords(stores) {
|
|
82
|
+
for (const language of LANGUAGES) {
|
|
83
|
+
const set = accepted.get(language);
|
|
84
|
+
set.clear();
|
|
85
|
+
const results = await Promise.allSettled(stores.map((store) => store.load(language)));
|
|
86
|
+
for (const result of results)
|
|
87
|
+
if (result.status === 'fulfilled')
|
|
88
|
+
for (const word of result.value)
|
|
89
|
+
set.add(word);
|
|
90
|
+
}
|
|
91
|
+
notify();
|
|
92
|
+
}
|
|
93
|
+
/** Adds a word to the first store that accepts writes. Returns false if none does. */
|
|
94
|
+
export async function addUserWord(stores, language, word) {
|
|
95
|
+
const writable = stores.find((store) => !store.readOnly && store.append);
|
|
96
|
+
if (!writable?.append)
|
|
97
|
+
return false;
|
|
98
|
+
await writable.append(language, word);
|
|
99
|
+
accepted.get(language)?.add(word);
|
|
100
|
+
notify();
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
function readIgnored() {
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(sessionStorage.getItem(SESSION_KEY) ?? '[]');
|
|
106
|
+
return Array.isArray(parsed) ? parsed.filter((e) => typeof e?.word === 'string') : [];
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Loads the session's ignored words. Call once at startup. */
|
|
113
|
+
export function restoreIgnoredWords() {
|
|
114
|
+
for (const { language, word } of readIgnored())
|
|
115
|
+
ignored.get(language)?.add(word);
|
|
116
|
+
notify();
|
|
117
|
+
}
|
|
118
|
+
/** Ignores a word for the rest of this browser session (kept in sessionStorage). */
|
|
119
|
+
export function ignoreWord(language, word) {
|
|
120
|
+
ignored.get(language)?.add(word);
|
|
121
|
+
const all = readIgnored();
|
|
122
|
+
if (!all.some((e) => e.language === language && e.word === word))
|
|
123
|
+
all.push({ language, word });
|
|
124
|
+
try {
|
|
125
|
+
sessionStorage.setItem(SESSION_KEY, JSON.stringify(all));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Session storage unavailable: the word is ignored until the page reloads.
|
|
129
|
+
}
|
|
130
|
+
notify();
|
|
131
|
+
}
|
|
132
|
+
restoreIgnoredWords();
|