@inshapardaz/likhari-react 0.1.21-dev.206 → 0.1.22
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 +0 -9
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +11 -12
- package/dist/components/Toolbar.d.ts +1 -4
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +2 -3
- package/dist/i18n/strings.d.ts +0 -14
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +5 -47
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/theme/editor.css +0 -58
- package/package.json +1 -1
- package/dist/autocorrect/AutoCorrectPlugin.d.ts +0 -27
- package/dist/autocorrect/AutoCorrectPlugin.d.ts.map +0 -1
- package/dist/autocorrect/AutoCorrectPlugin.js +0 -108
- package/dist/autocorrect/autoCorrectActions.d.ts +0 -35
- package/dist/autocorrect/autoCorrectActions.d.ts.map +0 -1
- package/dist/autocorrect/autoCorrectActions.js +0 -182
- package/dist/autocorrect/autoCorrectStores.d.ts +0 -52
- package/dist/autocorrect/autoCorrectStores.d.ts.map +0 -1
- package/dist/autocorrect/autoCorrectStores.js +0 -115
- package/dist/autocorrect/punctuationRules.d.ts +0 -26
- package/dist/autocorrect/punctuationRules.d.ts.map +0 -1
- package/dist/autocorrect/punctuationRules.js +0 -53
- package/dist/cleanup/textCleanupActions.d.ts +0 -8
- package/dist/cleanup/textCleanupActions.d.ts.map +0 -1
- package/dist/cleanup/textCleanupActions.js +0 -28
- package/dist/components/AutoCorrectPanel.d.ts +0 -15
- package/dist/components/AutoCorrectPanel.d.ts.map +0 -1
- package/dist/components/AutoCorrectPanel.js +0 -44
- package/dist/normalization/urduNormalize.d.ts +0 -24
- package/dist/normalization/urduNormalize.d.ts.map +0 -1
- package/dist/normalization/urduNormalize.js +0 -116
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { $createRangeSelection, $getNodeByKey, $getRoot, $getSelection, $isRangeSelection, $isTextNode, $setSelection, } from 'lexical';
|
|
2
|
-
import { $locate, $replaceMatch, $textGroups } from '../find/findReplaceActions.js';
|
|
3
|
-
import { wordsIn } from '../spellcheck/spellDictionaries.js';
|
|
4
|
-
import { spacePunctuationText } from './punctuationRules.js';
|
|
5
|
-
/** The part of a word that counts as word characters at its end. */
|
|
6
|
-
const WORD_END = /[\p{L}\p{M}'’]+$/u;
|
|
7
|
-
const WORD_CHAR = /[\p{L}\p{M}'’]/u;
|
|
8
|
-
/**
|
|
9
|
-
* Corrects the word that ends just before the caret, when the character just
|
|
10
|
-
* typed (the word boundary: space or punctuation) is not a word character. The
|
|
11
|
-
* correction replaces only the word, and the caret stays right after the
|
|
12
|
-
* boundary. Returns true if a correction was made.
|
|
13
|
-
*/
|
|
14
|
-
export function $correctWordBeforeCaret(table, normalize) {
|
|
15
|
-
const selection = $getSelection();
|
|
16
|
-
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
|
17
|
-
return false;
|
|
18
|
-
const anchor = selection.anchor;
|
|
19
|
-
const node = anchor.getNode();
|
|
20
|
-
if (!$isTextNode(node))
|
|
21
|
-
return false;
|
|
22
|
-
const before = node.getTextContent().slice(0, anchor.offset);
|
|
23
|
-
const boundary = before.slice(-1);
|
|
24
|
-
if (boundary === '' || WORD_CHAR.test(boundary))
|
|
25
|
-
return false;
|
|
26
|
-
const body = before.slice(0, -1);
|
|
27
|
-
const word = WORD_END.exec(body)?.[0];
|
|
28
|
-
if (!word)
|
|
29
|
-
return false;
|
|
30
|
-
const replacement = correctionFor(word, table, normalize);
|
|
31
|
-
if (replacement === undefined || replacement === word)
|
|
32
|
-
return false;
|
|
33
|
-
const start = body.length - word.length;
|
|
34
|
-
$replaceMatch({ anchorKey: node.getKey(), anchorOffset: start, focusKey: node.getKey(), focusOffset: body.length }, replacement);
|
|
35
|
-
// Put the caret back after the boundary the user typed, not at the end of the replacement.
|
|
36
|
-
const after = $getSelection();
|
|
37
|
-
if ($isRangeSelection(after)) {
|
|
38
|
-
const focusNode = after.focus.getNode();
|
|
39
|
-
const caret = after.focus.offset + boundary.length;
|
|
40
|
-
after.anchor.set(focusNode.getKey(), caret, 'text');
|
|
41
|
-
after.focus.set(focusNode.getKey(), caret, 'text');
|
|
42
|
-
}
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Corrects every whole word in the document, for text that never went through
|
|
47
|
-
* typing (pasted or loaded content). Words are found per paragraph or cell, so a
|
|
48
|
-
* word split by formatting is still one word. Each block uses the table for its
|
|
49
|
-
* direction. Returns how many words were corrected.
|
|
50
|
-
*/
|
|
51
|
-
export function $correctDocument(ltr, rtl, normalizeRtl) {
|
|
52
|
-
if (normalizeRtl) {
|
|
53
|
-
for (const node of $getRoot().getAllTextNodes()) {
|
|
54
|
-
if (node.getParentOrThrow().getDirection() !== 'rtl')
|
|
55
|
-
continue;
|
|
56
|
-
const text = node.getTextContent();
|
|
57
|
-
const normalized = normalizeRtl(text);
|
|
58
|
-
if (normalized !== text)
|
|
59
|
-
node.setTextContent(normalized);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
let corrected = 0;
|
|
63
|
-
for (const group of $textGroups()) {
|
|
64
|
-
const rightToLeft = group[0].getParentOrThrow().getDirection() === 'rtl';
|
|
65
|
-
const table = rightToLeft ? rtl : ltr;
|
|
66
|
-
const text = group.map((node) => node.getTextContent()).join('');
|
|
67
|
-
const spans = wordsIn(text)
|
|
68
|
-
.map((span) => ({ span, replacement: correctionFor(span.word, table, rightToLeft ? normalizeRtl : undefined) }))
|
|
69
|
-
.filter((item) => item.replacement !== undefined && item.replacement !== item.span.word);
|
|
70
|
-
// Right to left, so earlier positions stay valid as each word is replaced.
|
|
71
|
-
for (const { span, replacement } of spans.reverse()) {
|
|
72
|
-
const start = $locate(group, span.start, false);
|
|
73
|
-
const end = $locate(group, span.end, true);
|
|
74
|
-
$replaceMatch({ anchorKey: start.key, anchorOffset: start.offset, focusKey: end.key, focusOffset: end.offset }, replacement);
|
|
75
|
-
corrected += 1;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return corrected;
|
|
79
|
-
}
|
|
80
|
-
/** What a word should become: its normalised form, then any table correction for that. */
|
|
81
|
-
function correctionFor(word, table, normalize) {
|
|
82
|
-
const normalized = normalize ? normalize(word) : word;
|
|
83
|
-
return table.get(normalized) ?? (normalized !== word ? normalized : undefined);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Applies a punctuation rule to the text just before the caret, when that text
|
|
87
|
-
* ends with a rule's incorrect form. Longest match first. The caret ends after
|
|
88
|
-
* the replacement.
|
|
89
|
-
*/
|
|
90
|
-
export function $correctPunctuationBeforeCaret(rules) {
|
|
91
|
-
const selection = $getSelection();
|
|
92
|
-
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
|
93
|
-
return false;
|
|
94
|
-
const anchor = selection.anchor;
|
|
95
|
-
const node = anchor.getNode();
|
|
96
|
-
if (!$isTextNode(node))
|
|
97
|
-
return false;
|
|
98
|
-
const before = node.getTextContent().slice(0, anchor.offset);
|
|
99
|
-
const rule = rules.find((r) => before.endsWith(r.incorrect) && (!r.completeWord || !WORD_CHAR.test(before.charAt(before.length - r.incorrect.length - 1) || ' ')));
|
|
100
|
-
if (!rule)
|
|
101
|
-
return false;
|
|
102
|
-
const start = before.length - rule.incorrect.length;
|
|
103
|
-
$replaceMatch({ anchorKey: node.getKey(), anchorOffset: start, focusKey: node.getKey(), focusOffset: before.length }, rule.correct);
|
|
104
|
-
const after = $getSelection();
|
|
105
|
-
if ($isRangeSelection(after)) {
|
|
106
|
-
const caret = start + rule.correct.length;
|
|
107
|
-
after.anchor.set(node.getKey(), caret, 'text');
|
|
108
|
-
after.focus.set(node.getKey(), caret, 'text');
|
|
109
|
-
}
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Applies the punctuation rules to every paragraph and cell in the document.
|
|
114
|
-
* Where two matches overlap, the longer one wins. Returns how many were applied.
|
|
115
|
-
*/
|
|
116
|
-
export function $correctPunctuationDocument(rules) {
|
|
117
|
-
let applied = 0;
|
|
118
|
-
for (const group of $textGroups()) {
|
|
119
|
-
const text = group.map((node) => node.getTextContent()).join('');
|
|
120
|
-
const candidates = [];
|
|
121
|
-
for (const rule of rules) {
|
|
122
|
-
let index = text.indexOf(rule.incorrect);
|
|
123
|
-
while (index !== -1) {
|
|
124
|
-
candidates.push({ start: index, end: index + rule.incorrect.length, correct: rule.correct });
|
|
125
|
-
index = text.indexOf(rule.incorrect, index + 1);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// Longest first, then earliest; keep only matches that do not overlap a kept one.
|
|
129
|
-
candidates.sort((a, b) => b.end - b.start - (a.end - a.start) || a.start - b.start);
|
|
130
|
-
const kept = [];
|
|
131
|
-
for (const c of candidates) {
|
|
132
|
-
if (!kept.some((k) => c.start < k.end && k.start < c.end))
|
|
133
|
-
kept.push(c);
|
|
134
|
-
}
|
|
135
|
-
// Right to left, so earlier positions stay valid as each match is replaced.
|
|
136
|
-
for (const c of kept.sort((a, b) => b.start - a.start)) {
|
|
137
|
-
const start = $locate(group, c.start, false);
|
|
138
|
-
const end = $locate(group, c.end, true);
|
|
139
|
-
$replaceMatch({ anchorKey: start.key, anchorOffset: start.offset, focusKey: end.key, focusOffset: end.offset }, c.correct);
|
|
140
|
-
applied += 1;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return applied;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Runs `change` and then puts the caret back where it was. Replacements move the
|
|
147
|
-
* selection onto each match they make, so a whole-document pass would otherwise
|
|
148
|
-
* leave the caret at the last match, or at the end of the document.
|
|
149
|
-
*/
|
|
150
|
-
export function $preservingSelection(change) {
|
|
151
|
-
const before = $getSelection();
|
|
152
|
-
const saved = $isRangeSelection(before)
|
|
153
|
-
? {
|
|
154
|
-
anchor: { key: before.anchor.key, offset: before.anchor.offset, type: before.anchor.type },
|
|
155
|
-
focus: { key: before.focus.key, offset: before.focus.offset, type: before.focus.type },
|
|
156
|
-
}
|
|
157
|
-
: null;
|
|
158
|
-
change();
|
|
159
|
-
if (!saved) {
|
|
160
|
-
$setSelection(null);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
if (!$getNodeByKey(saved.anchor.key) || !$getNodeByKey(saved.focus.key))
|
|
164
|
-
return;
|
|
165
|
-
const selection = $createRangeSelection();
|
|
166
|
-
selection.anchor.set(saved.anchor.key, saved.anchor.offset, saved.anchor.type);
|
|
167
|
-
selection.focus.set(saved.focus.key, saved.focus.offset, saved.focus.type);
|
|
168
|
-
$setSelection(selection);
|
|
169
|
-
}
|
|
170
|
-
/** Applies urduhack-style punctuation spacing to every text node. Returns how many changed. */
|
|
171
|
-
export function $spacePunctuationDocument() {
|
|
172
|
-
let changed = 0;
|
|
173
|
-
for (const node of $getRoot().getAllTextNodes()) {
|
|
174
|
-
const text = node.getTextContent();
|
|
175
|
-
const spaced = spacePunctuationText(text);
|
|
176
|
-
if (spaced !== text) {
|
|
177
|
-
node.setTextContent(spaced);
|
|
178
|
-
changed += 1;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return changed;
|
|
182
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { SpellLanguage } from '../spellcheck/spellDictionaries.js';
|
|
2
|
-
/** One correction: a typed word and what it should become. */
|
|
3
|
-
export interface AutoCorrectEntry {
|
|
4
|
-
from: string;
|
|
5
|
-
to: string;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Where auto-corrections come from and go. The host supplies any number of
|
|
9
|
-
* stores; corrections from all of them are merged, and when two stores
|
|
10
|
-
* correct the same word, the one listed first wins. New corrections are
|
|
11
|
-
* appended to the first store that accepts writes.
|
|
12
|
-
*/
|
|
13
|
-
export interface AutoCorrectStore {
|
|
14
|
-
readonly id: string;
|
|
15
|
-
/** A store without `append` (or with `readOnly`) is never written to. */
|
|
16
|
-
readonly readOnly?: boolean;
|
|
17
|
-
load(language: SpellLanguage): Promise<AutoCorrectEntry[]>;
|
|
18
|
-
append?(language: SpellLanguage, entry: AutoCorrectEntry): Promise<void>;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Corrections kept in this browser's localStorage. Storage can be disabled or
|
|
22
|
-
* full, so every access is guarded: a failure means "no corrections", never a
|
|
23
|
-
* broken editor.
|
|
24
|
-
*/
|
|
25
|
-
export declare function localStorageAutoCorrectStore(options?: {
|
|
26
|
-
key?: string;
|
|
27
|
-
}): AutoCorrectStore;
|
|
28
|
-
/**
|
|
29
|
-
* Corrections served by an API. `load` calls `GET {url}?language=xx` and expects
|
|
30
|
-
* a JSON array of `{ from, to }`; `append` calls `POST {url}` with
|
|
31
|
-
* `{ language, from, to }`. Credentials and headers belong to the host.
|
|
32
|
-
*/
|
|
33
|
-
export declare function apiAutoCorrectStore(options: {
|
|
34
|
-
id?: string;
|
|
35
|
-
url: string;
|
|
36
|
-
headers?: Record<string, string>;
|
|
37
|
-
fetch?: typeof fetch;
|
|
38
|
-
}): AutoCorrectStore;
|
|
39
|
-
/**
|
|
40
|
-
* Corrections from a JSON file the host publishes — a list of `{ language, from, to }`.
|
|
41
|
-
* Read-only: it has no way to write back to a static file.
|
|
42
|
-
*/
|
|
43
|
-
export declare function fileAutoCorrectStore(options: {
|
|
44
|
-
id?: string;
|
|
45
|
-
url: string;
|
|
46
|
-
fetch?: typeof fetch;
|
|
47
|
-
}): AutoCorrectStore;
|
|
48
|
-
/** Corrections for one language, merged across stores. Failing stores are skipped. */
|
|
49
|
-
export declare function loadAutoCorrections(stores: AutoCorrectStore[], language: SpellLanguage): Promise<Map<string, string>>;
|
|
50
|
-
/** Saves a correction to the first store that accepts writes. Returns false if none does. */
|
|
51
|
-
export declare function appendAutoCorrection(stores: AutoCorrectStore[], language: SpellLanguage, entry: AutoCorrectEntry): Promise<boolean>;
|
|
52
|
-
//# sourceMappingURL=autoCorrectStores.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"autoCorrectStores.d.ts","sourceRoot":"","sources":["../../src/autocorrect/autoCorrectStores.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1E;AAkBD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,gBAAgB,CA6B7F;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,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,gBAAgB,CAmBnB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CAAE,GAAG,gBAAgB,CAkBlH;AAED,sFAAsF;AACtF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAQ3H;AAED,6FAA6F;AAC7F,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAKzI"}
|
|
@@ -1,115 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,53 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"textCleanupActions.d.ts","sourceRoot":"","sources":["../../src/cleanup/textCleanupActions.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAMrC"}
|
|
@@ -1,28 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { Strings } from '../i18n/strings.js';
|
|
2
|
-
import { type AutoCorrectStore } from '../autocorrect/autoCorrectStores.js';
|
|
3
|
-
/**
|
|
4
|
-
* Adds one correction (a typed word and its replacement) to the first store
|
|
5
|
-
* that accepts writes. Saving bumps the editor's correction version, so the new
|
|
6
|
-
* entry applies to the next word the user types.
|
|
7
|
-
*/
|
|
8
|
-
export declare function AutoCorrectPanel({ strings, dir, stores, onSaved, onClose, }: {
|
|
9
|
-
strings: Strings;
|
|
10
|
-
dir: 'ltr' | 'rtl';
|
|
11
|
-
stores: AutoCorrectStore[];
|
|
12
|
-
onSaved: () => void;
|
|
13
|
-
onClose: () => void;
|
|
14
|
-
}): import("react").JSX.Element;
|
|
15
|
-
//# sourceMappingURL=AutoCorrectPanel.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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;AAS/F;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,GAAG,EACH,MAAM,EACN,OAAO,EACP,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,KAAK,GAAG,KAAK,CAAC;IACnB,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAqEA"}
|
|
@@ -1,44 +0,0 @@
|
|
|
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, onSaved, onClose, }) {
|
|
16
|
-
const t = strings.autoCorrect;
|
|
17
|
-
const [language, setLanguage] = useState('en');
|
|
18
|
-
const [from, setFrom] = useState('');
|
|
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,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Urdu character normalisation, ported from urduhack's `normalization/character.py`
|
|
3
|
-
* (MIT licence, https://github.com/urduhack/urduhack). The mapping replaces
|
|
4
|
-
* Arabic and presentation-form letters with the standard Urdu letter, and
|
|
5
|
-
* the tables below keep the same entries and order as the original, so the
|
|
6
|
-
* output matches it character for character.
|
|
7
|
-
*/
|
|
8
|
-
/** Replaces Arabic and presentation-form letters with standard Urdu letters, character by character. */
|
|
9
|
-
export declare function normalizeUrduCharacters(text: string): string;
|
|
10
|
-
export declare function removeUrduDiacritics(text: string): string;
|
|
11
|
-
/** Converts Urdu digits to English digits, or the reverse. */
|
|
12
|
-
export declare function replaceUrduDigits(text: string, to: 'english' | 'urdu'): string;
|
|
13
|
-
export interface UrduNormalizationOptions {
|
|
14
|
-
/** Remove harakat. Off by default, since they are meaningful in poetry and religious text. */
|
|
15
|
-
removeDiacritics?: boolean;
|
|
16
|
-
/** Digit style to convert to; 'keep' (the default) leaves digits as they are. */
|
|
17
|
-
digits?: 'keep' | 'english' | 'urdu';
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* The normalisation urduhack's `normalize` applies, with the diacritic and
|
|
21
|
-
* digit steps made optional. Character mapping always runs.
|
|
22
|
-
*/
|
|
23
|
-
export declare function normalizeUrdu(text: string, options?: UrduNormalizationOptions): string;
|
|
24
|
-
//# sourceMappingURL=urduNormalize.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"urduNormalize.d.ts","sourceRoot":"","sources":["../../src/normalization/urduNormalize.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA8EH,wGAAwG;AACxG,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI5D;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAS9E;AAED,MAAM,WAAW,wBAAwB;IACvC,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAG,MAAM,CAK1F"}
|