@inshapardaz/likhari-react 0.1.22 → 0.1.23-dev.221
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 +12 -0
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +24 -11
- package/dist/autocorrect/AutoCorrectPlugin.d.ts +27 -0
- package/dist/autocorrect/AutoCorrectPlugin.d.ts.map +1 -0
- package/dist/autocorrect/AutoCorrectPlugin.js +124 -0
- package/dist/autocorrect/autoCorrectActions.d.ts +41 -0
- package/dist/autocorrect/autoCorrectActions.d.ts.map +1 -0
- package/dist/autocorrect/autoCorrectActions.js +212 -0
- package/dist/autocorrect/autoCorrectStores.d.ts +52 -0
- package/dist/autocorrect/autoCorrectStores.d.ts.map +1 -0
- package/dist/autocorrect/autoCorrectStores.js +115 -0
- package/dist/autocorrect/punctuationRules.d.ts +26 -0
- package/dist/autocorrect/punctuationRules.d.ts.map +1 -0
- package/dist/autocorrect/punctuationRules.js +53 -0
- package/dist/cleanup/textCleanupActions.d.ts +8 -0
- package/dist/cleanup/textCleanupActions.d.ts.map +1 -0
- package/dist/cleanup/textCleanupActions.js +28 -0
- package/dist/components/AutoCorrectPanel.d.ts +19 -0
- package/dist/components/AutoCorrectPanel.d.ts.map +1 -0
- package/dist/components/AutoCorrectPanel.js +44 -0
- 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 +10 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +128 -109
- package/dist/i18n/strings.d.ts +25 -0
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +80 -5
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/normalization/urduNormalize.d.ts +24 -0
- package/dist/normalization/urduNormalize.d.ts.map +1 -0
- package/dist/normalization/urduNormalize.js +116 -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/dist/theme/editor.css +58 -0
- package/package.json +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const STORAGE_KEY = 'likhari-autocorrect';
|
|
2
|
+
function isEntry(value) {
|
|
3
|
+
return (typeof value === 'object' &&
|
|
4
|
+
value !== null &&
|
|
5
|
+
typeof value.from === 'string' &&
|
|
6
|
+
typeof value.to === 'string' &&
|
|
7
|
+
value.from !== '');
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Corrections kept in this browser's localStorage. Storage can be disabled or
|
|
11
|
+
* full, so every access is guarded: a failure means "no corrections", never a
|
|
12
|
+
* broken editor.
|
|
13
|
+
*/
|
|
14
|
+
export function localStorageAutoCorrectStore(options = {}) {
|
|
15
|
+
const key = options.key ?? STORAGE_KEY;
|
|
16
|
+
function readAll() {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(localStorage.getItem(key) ?? '[]');
|
|
19
|
+
return Array.isArray(parsed) ? parsed.filter((e) => isEntry(e) && 'language' in e) : [];
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
id: 'local-storage',
|
|
27
|
+
async load(language) {
|
|
28
|
+
return readAll()
|
|
29
|
+
.filter((e) => e.language === language)
|
|
30
|
+
.map(({ from, to }) => ({ from, to }));
|
|
31
|
+
},
|
|
32
|
+
async append(language, entry) {
|
|
33
|
+
const all = readAll().filter((e) => !(e.language === language && e.from === entry.from));
|
|
34
|
+
all.push({ language, from: entry.from, to: entry.to });
|
|
35
|
+
try {
|
|
36
|
+
localStorage.setItem(key, JSON.stringify(all));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Quota exceeded or storage disabled: the correction is kept for this session only.
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Corrections served by an API. `load` calls `GET {url}?language=xx` and expects
|
|
46
|
+
* a JSON array of `{ from, to }`; `append` calls `POST {url}` with
|
|
47
|
+
* `{ language, from, to }`. Credentials and headers belong to the host.
|
|
48
|
+
*/
|
|
49
|
+
export function apiAutoCorrectStore(options) {
|
|
50
|
+
const doFetch = options.fetch ?? fetch;
|
|
51
|
+
return {
|
|
52
|
+
id: options.id ?? 'api',
|
|
53
|
+
async load(language) {
|
|
54
|
+
const response = await doFetch(`${options.url}?language=${encodeURIComponent(language)}`, { headers: options.headers });
|
|
55
|
+
if (!response.ok)
|
|
56
|
+
throw new Error(`Auto-correct API failed: ${response.status}`);
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
return Array.isArray(data) ? data.filter(isEntry).map(({ from, to }) => ({ from, to })) : [];
|
|
59
|
+
},
|
|
60
|
+
async append(language, entry) {
|
|
61
|
+
const response = await doFetch(options.url, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
64
|
+
body: JSON.stringify({ language, from: entry.from, to: entry.to }),
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok)
|
|
67
|
+
throw new Error(`Auto-correct API rejected the entry: ${response.status}`);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Corrections from a JSON file the host publishes — a list of `{ language, from, to }`.
|
|
73
|
+
* Read-only: it has no way to write back to a static file.
|
|
74
|
+
*/
|
|
75
|
+
export function fileAutoCorrectStore(options) {
|
|
76
|
+
const doFetch = options.fetch ?? fetch;
|
|
77
|
+
let cached = null;
|
|
78
|
+
const all = () => {
|
|
79
|
+
cached ?? (cached = doFetch(options.url).then(async (response) => {
|
|
80
|
+
if (!response.ok)
|
|
81
|
+
throw new Error(`Auto-correct file failed: ${response.status}`);
|
|
82
|
+
const data = await response.json();
|
|
83
|
+
return Array.isArray(data) ? data.filter((e) => isEntry(e) && 'language' in e) : [];
|
|
84
|
+
}));
|
|
85
|
+
return cached;
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
id: options.id ?? 'file',
|
|
89
|
+
readOnly: true,
|
|
90
|
+
async load(language) {
|
|
91
|
+
return (await all()).filter((e) => e.language === language).map(({ from, to }) => ({ from, to }));
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** Corrections for one language, merged across stores. Failing stores are skipped. */
|
|
96
|
+
export async function loadAutoCorrections(stores, language) {
|
|
97
|
+
const table = new Map();
|
|
98
|
+
const results = await Promise.allSettled(stores.map((store) => store.load(language)));
|
|
99
|
+
results.forEach((result) => {
|
|
100
|
+
if (result.status !== 'fulfilled')
|
|
101
|
+
return;
|
|
102
|
+
for (const entry of result.value)
|
|
103
|
+
if (!table.has(entry.from))
|
|
104
|
+
table.set(entry.from, entry.to);
|
|
105
|
+
});
|
|
106
|
+
return table;
|
|
107
|
+
}
|
|
108
|
+
/** Saves a correction to the first store that accepts writes. Returns false if none does. */
|
|
109
|
+
export async function appendAutoCorrection(stores, language, entry) {
|
|
110
|
+
const writable = stores.find((store) => !store.readOnly && store.append);
|
|
111
|
+
if (!writable?.append)
|
|
112
|
+
return false;
|
|
113
|
+
await writable.append(language, entry);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common Urdu punctuation and character fixes, as a snapshot of
|
|
3
|
+
* https://api.nawishta.co.uk/tools/ur/spellchecker/punctuation (fetched 2026-10-04).
|
|
4
|
+
* Bundled so the editor works offline and does not depend on that service;
|
|
5
|
+
* refresh the list when the service changes.
|
|
6
|
+
*/
|
|
7
|
+
export interface PunctuationRule {
|
|
8
|
+
incorrect: string;
|
|
9
|
+
correct: string;
|
|
10
|
+
/** When true, the match must be a whole word (not part of a longer one). */
|
|
11
|
+
completeWord: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const URDU_PUNCTUATION_RULES: PunctuationRule[];
|
|
14
|
+
export interface PunctuationOptions {
|
|
15
|
+
/** Apply the punctuation fixes. On by default. */
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
/** Replace a straight double quote (") with a closing curly quote (”). On by default. */
|
|
18
|
+
straightDoubleQuote?: boolean;
|
|
19
|
+
/** Space after Urdu punctuation and remove spaces before it, as urduhack's punctuations_space. Off by default. */
|
|
20
|
+
spacing?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** The rules in use for the options, longest match first, so `۔"` wins over `"`. */
|
|
23
|
+
export declare function activePunctuationRules(options?: PunctuationOptions): PunctuationRule[];
|
|
24
|
+
/** Spaces after Urdu punctuation (not inside numbers), and none before it. */
|
|
25
|
+
export declare function spacePunctuationText(text: string): string;
|
|
26
|
+
//# sourceMappingURL=punctuationRules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"punctuationRules.d.ts","sourceRoot":"","sources":["../../src/autocorrect/punctuationRules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,sBAAsB,EAAE,eAAe,EAmCnD,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yFAAyF;IACzF,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,kHAAkH;IAClH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,oFAAoF;AACpF,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,kBAAuB,GAAG,eAAe,EAAE,CAK1F;AAOD,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const URDU_PUNCTUATION_RULES = [
|
|
2
|
+
{ incorrect: ".", correct: "۔", completeWord: false },
|
|
3
|
+
{ incorrect: ",", correct: "،", completeWord: false },
|
|
4
|
+
{ incorrect: " ۔", correct: "۔", completeWord: false },
|
|
5
|
+
{ incorrect: " - ", correct: "۔ ", completeWord: false },
|
|
6
|
+
{ incorrect: " ،", correct: "،", completeWord: false },
|
|
7
|
+
{ incorrect: "?", correct: "؟", completeWord: false },
|
|
8
|
+
{ incorrect: " ؟", correct: "؟", completeWord: false },
|
|
9
|
+
{ incorrect: " !", correct: "!", completeWord: false },
|
|
10
|
+
{ incorrect: "( ", correct: "(", completeWord: false },
|
|
11
|
+
{ incorrect: " (", correct: "(", completeWord: false },
|
|
12
|
+
{ incorrect: "ه", correct: "ہ", completeWord: false },
|
|
13
|
+
{ incorrect: "ک", correct: "ک", completeWord: false },
|
|
14
|
+
{ incorrect: "ئو", correct: "ؤ", completeWord: false },
|
|
15
|
+
{ incorrect: "’’", correct: "”", completeWord: false },
|
|
16
|
+
{ incorrect: "‘‘", correct: "“", completeWord: false },
|
|
17
|
+
{ incorrect: "…", correct: "۔۔۔", completeWord: false },
|
|
18
|
+
{ incorrect: "——", correct: "۔۔۔", completeWord: false },
|
|
19
|
+
{ incorrect: "—", correct: "۔۔۔", completeWord: false },
|
|
20
|
+
{ incorrect: " ۔۔۔", correct: "۔۔۔ ", completeWord: false },
|
|
21
|
+
{ incorrect: "،،", correct: "“", completeWord: false },
|
|
22
|
+
{ incorrect: "۔\"", correct: "۔“", completeWord: false },
|
|
23
|
+
{ incorrect: "، \"", correct: "۔ ”", completeWord: false },
|
|
24
|
+
{ incorrect: "؟\"", correct: "؟“", completeWord: false },
|
|
25
|
+
{ incorrect: "!\"", correct: "!“", completeWord: false },
|
|
26
|
+
{ incorrect: "-", correct: "۔", completeWord: false },
|
|
27
|
+
{ incorrect: " ، ", correct: "، ", completeWord: false },
|
|
28
|
+
{ incorrect: " ۔", correct: "۔ ", completeWord: false },
|
|
29
|
+
{ incorrect: " : ", correct: ": ", completeWord: false },
|
|
30
|
+
{ incorrect: "آ", correct: "آ", completeWord: false },
|
|
31
|
+
{ incorrect: "ؤ", correct: "ؤ", completeWord: false },
|
|
32
|
+
{ incorrect: " ِ", correct: "ِ", completeWord: false },
|
|
33
|
+
{ incorrect: " ً", correct: "ً", completeWord: false },
|
|
34
|
+
{ incorrect: " :", correct: ":", completeWord: false },
|
|
35
|
+
{ incorrect: "” ", correct: "”", completeWord: false },
|
|
36
|
+
];
|
|
37
|
+
/** The rules in use for the options, longest match first, so `۔"` wins over `"`. */
|
|
38
|
+
export function activePunctuationRules(options = {}) {
|
|
39
|
+
if (options.enabled === false)
|
|
40
|
+
return [];
|
|
41
|
+
const rules = URDU_PUNCTUATION_RULES.filter((rule) => rule.incorrect !== rule.correct);
|
|
42
|
+
if (options.straightDoubleQuote !== false)
|
|
43
|
+
rules.push({ incorrect: '"', correct: '”', completeWord: false });
|
|
44
|
+
return rules.sort((a, b) => b.incorrect.length - a.incorrect.length);
|
|
45
|
+
}
|
|
46
|
+
/** Space after Urdu punctuation, when the next character is not punctuation, a digit or a space. */
|
|
47
|
+
const SPACE_AFTER_PUNCTUATION = /(?<=[؛،٫؟۔٪])(?=[^؛،٫؟۔٪0-9 \n])/gu;
|
|
48
|
+
/** Spaces before Urdu punctuation. */
|
|
49
|
+
const SPACE_BEFORE_PUNCTUATION = /\s+([؛،٫؟۔٪])/gu;
|
|
50
|
+
/** Spaces after Urdu punctuation (not inside numbers), and none before it. */
|
|
51
|
+
export function spacePunctuationText(text) {
|
|
52
|
+
return text.replace(SPACE_AFTER_PUNCTUATION, ' ').replace(SPACE_BEFORE_PUNCTUATION, '$1');
|
|
53
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tidies the whole document in place: Unicode NFC normalisation (Arabic-script
|
|
3
|
+
* text can store the same visible letters in different code points), collapsed
|
|
4
|
+
* repeated spaces, and empty text nodes removed. Formatting is kept, since each
|
|
5
|
+
* text node is only changed, never replaced. Returns how many text nodes changed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function $cleanUpText(): number;
|
|
8
|
+
//# sourceMappingURL=textCleanupActions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textCleanupActions.d.ts","sourceRoot":"","sources":["../../src/cleanup/textCleanupActions.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAMrC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { $getRoot } from 'lexical';
|
|
2
|
+
/** Runs of two or more spaces or tabs, collapsed to one space. */
|
|
3
|
+
const REPEATED_SPACE = /[ \t]{2,}/g;
|
|
4
|
+
/**
|
|
5
|
+
* Tidies the whole document in place: Unicode NFC normalisation (Arabic-script
|
|
6
|
+
* text can store the same visible letters in different code points), collapsed
|
|
7
|
+
* repeated spaces, and empty text nodes removed. Formatting is kept, since each
|
|
8
|
+
* text node is only changed, never replaced. Returns how many text nodes changed.
|
|
9
|
+
*/
|
|
10
|
+
export function $cleanUpText() {
|
|
11
|
+
let changed = 0;
|
|
12
|
+
for (const node of $getRoot().getAllTextNodes()) {
|
|
13
|
+
if (cleanNode(node))
|
|
14
|
+
changed += 1;
|
|
15
|
+
}
|
|
16
|
+
return changed;
|
|
17
|
+
}
|
|
18
|
+
function cleanNode(node) {
|
|
19
|
+
const text = node.getTextContent();
|
|
20
|
+
const cleaned = text.normalize('NFC').replace(REPEATED_SPACE, ' ');
|
|
21
|
+
if (cleaned === text)
|
|
22
|
+
return false;
|
|
23
|
+
if (cleaned === '')
|
|
24
|
+
node.remove();
|
|
25
|
+
else
|
|
26
|
+
node.setTextContent(cleaned);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Strings } from '../i18n/strings.js';
|
|
2
|
+
import { type AutoCorrectStore } from '../autocorrect/autoCorrectStores.js';
|
|
3
|
+
import type { SpellLanguage } from '../spellcheck/spellDictionaries.js';
|
|
4
|
+
/**
|
|
5
|
+
* Adds one correction (a typed word and its replacement) to the first store
|
|
6
|
+
* that accepts writes. Saving bumps the editor's correction version, so the new
|
|
7
|
+
* entry applies to the next word the user types.
|
|
8
|
+
*/
|
|
9
|
+
export declare function AutoCorrectPanel({ strings, dir, stores, initialFrom, initialLanguage, onSaved, onClose, }: {
|
|
10
|
+
strings: Strings;
|
|
11
|
+
dir: 'ltr' | 'rtl';
|
|
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;
|
|
16
|
+
onSaved: () => void;
|
|
17
|
+
onClose: () => void;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
19
|
+
//# sourceMappingURL=AutoCorrectPanel.d.ts.map
|
|
@@ -0,0 +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;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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { IconX } from '@tabler/icons-react';
|
|
4
|
+
import { appendAutoCorrection } from '../autocorrect/autoCorrectStores.js';
|
|
5
|
+
const LANGUAGES = [
|
|
6
|
+
{ value: 'en', label: 'English' },
|
|
7
|
+
{ value: 'ur', label: 'اردو' },
|
|
8
|
+
{ value: 'pa-shahmukhi', label: 'پنجابی (شاہ مکھی)' },
|
|
9
|
+
];
|
|
10
|
+
/**
|
|
11
|
+
* Adds one correction (a typed word and its replacement) to the first store
|
|
12
|
+
* that accepts writes. Saving bumps the editor's correction version, so the new
|
|
13
|
+
* entry applies to the next word the user types.
|
|
14
|
+
*/
|
|
15
|
+
export function AutoCorrectPanel({ strings, dir, stores, initialFrom = '', initialLanguage = 'en', onSaved, onClose, }) {
|
|
16
|
+
const t = strings.autoCorrect;
|
|
17
|
+
const [language, setLanguage] = useState(initialLanguage);
|
|
18
|
+
const [from, setFrom] = useState(initialFrom);
|
|
19
|
+
const [to, setTo] = useState('');
|
|
20
|
+
const [status, setStatus] = useState('idle');
|
|
21
|
+
const save = async () => {
|
|
22
|
+
const entry = { from: from.trim(), to: to.trim() };
|
|
23
|
+
if (entry.from === '' || entry.to === '' || entry.from === entry.to)
|
|
24
|
+
return;
|
|
25
|
+
try {
|
|
26
|
+
const written = await appendAutoCorrection(stores, language, entry);
|
|
27
|
+
if (!written) {
|
|
28
|
+
setStatus('nowhere');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
setStatus('saved');
|
|
32
|
+
setFrom('');
|
|
33
|
+
setTo('');
|
|
34
|
+
onSaved();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
setStatus('failed');
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return (_jsxs("div", { className: "likhari-autocorrect-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("label", { className: "likhari-autocorrect-field", children: [t.language, _jsx("select", { value: language, onChange: (e) => {
|
|
41
|
+
setLanguage(e.target.value);
|
|
42
|
+
setStatus('idle');
|
|
43
|
+
}, children: LANGUAGES.map((l) => (_jsx("option", { value: l.value, children: l.label }, l.value))) })] }), _jsxs("label", { className: "likhari-autocorrect-field", children: [t.from, _jsx("input", { value: from, onChange: (e) => setFrom(e.target.value), placeholder: t.fromPlaceholder })] }), _jsxs("label", { className: "likhari-autocorrect-field", children: [t.to, _jsx("input", { value: to, onChange: (e) => setTo(e.target.value), placeholder: t.toPlaceholder })] }), _jsxs("div", { className: "likhari-autocorrect-actions", children: [_jsx("button", { type: "button", onClick: save, disabled: from.trim() === '' || to.trim() === '', children: t.save }), _jsxs("span", { className: "likhari-spell-note", "aria-live": "polite", children: [status === 'saved' && t.saved, status === 'nowhere' && t.noWritableStore, status === 'failed' && t.failed] })] })] }));
|
|
44
|
+
}
|
|
@@ -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,15 +1,24 @@
|
|
|
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;
|
|
10
16
|
/** Whether the spellcheck panel is open, and how to toggle it (owned by EditorRoot). */
|
|
11
17
|
spellOpen?: boolean;
|
|
12
18
|
onToggleSpell?: () => void;
|
|
19
|
+
/** Whether the add-auto-correction panel is open, and how to toggle it (owned by EditorRoot). */
|
|
20
|
+
autoCorrectOpen?: boolean;
|
|
21
|
+
onToggleAutoCorrect?: () => void;
|
|
13
22
|
onSave?: () => void;
|
|
14
23
|
isDirty?: boolean;
|
|
15
24
|
showSave?: boolean;
|
|
@@ -22,5 +31,5 @@ export interface ToolbarProps {
|
|
|
22
31
|
/** Autosave drafts: when set, the toolbar offers a Drafts button that lists and restores them. */
|
|
23
32
|
drafts?: DraftsToolbarOptions;
|
|
24
33
|
}
|
|
25
|
-
export declare function Toolbar({ config, onSave, isDirty, showSave, findOpen, onToggleFind, spellOpen, onToggleSpell, 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;
|
|
26
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"}
|