@remit/ui 0.0.120 → 0.0.122
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/package.json +4 -1
- package/src/components/compose-body.spellcheck.test.ts +5 -2
- package/src/components/rich-text-editor.stories.tsx +190 -16
- package/src/components/rich-text-editor.tsx +72 -29
- package/src/components/rich-text-spellcheck-double.ts +65 -0
- package/src/components/rich-text-spellcheck-engine.ts +267 -0
- package/src/components/rich-text-spellcheck-languages.ts +62 -0
- package/src/components/rich-text-spellcheck-menu.test.ts +6 -7
- package/src/components/rich-text-spellcheck-notice.tsx +168 -0
- package/src/components/rich-text-spellcheck-provider.test.ts +296 -197
- package/src/components/rich-text-spellcheck-provider.ts +64 -10
- package/src/components/rich-text-spellcheck-words.ts +16 -354
- package/src/components/rich-text-spellcheck-worker-provider.ts +17 -7
- package/src/components/rich-text-spellcheck-worker.test.ts +253 -0
- package/src/components/rich-text-spellcheck-worker.ts +63 -15
- package/src/components/rich-text-spellcheck.test.ts +123 -9
- package/src/components/rich-text-spellcheck.ts +31 -2
- package/src/components/selection-wizard.render.test.ts +42 -1
- package/src/components/selection-wizard.tsx +50 -29
- package/src/lib/wizard-steps.test.ts +11 -0
- package/src/lib/wizard-steps.ts +21 -0
- package/src/rich-text.ts +1 -1
|
@@ -27,17 +27,39 @@ export interface SpellWorkerPort {
|
|
|
27
27
|
*/
|
|
28
28
|
export const SUGGEST_DEADLINE_MS = 5000;
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* How long a pass waits before the checker is called stopped. Hunspell answers
|
|
32
|
+
* a paragraph in single-digit milliseconds, so anything past this is a wedged
|
|
33
|
+
* engine rather than a slow one — and a wedged engine is invisible: the marks
|
|
34
|
+
* simply stop moving while `spellcheck` stays off, which on screen is text with
|
|
35
|
+
* nothing wrong in it. The deadline is what turns that into a failure the
|
|
36
|
+
* writer is told about and the browser's own checker takes back.
|
|
37
|
+
*/
|
|
38
|
+
export const CHECK_DEADLINE_MS = 5000;
|
|
39
|
+
|
|
40
|
+
interface Waiting {
|
|
41
|
+
settle(response: CheckResponse): void;
|
|
42
|
+
drop(): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
export const openSpellProvider = (
|
|
31
46
|
language: LanguageTag,
|
|
47
|
+
base: string,
|
|
32
48
|
port: SpellWorkerPort,
|
|
49
|
+
bytesExpected = 0,
|
|
33
50
|
): SpellProvider => {
|
|
34
|
-
const pending = new Map<string,
|
|
51
|
+
const pending = new Map<string, Waiting>();
|
|
35
52
|
const asking = new Map<
|
|
36
53
|
string,
|
|
37
54
|
{ settle(response: SuggestResponse): void; abandon(reason: Error): void }
|
|
38
55
|
>();
|
|
39
56
|
const listeners = new Set<(status: ProviderStatus) => void>();
|
|
40
|
-
let status: ProviderStatus = {
|
|
57
|
+
let status: ProviderStatus = {
|
|
58
|
+
state: "opening",
|
|
59
|
+
language,
|
|
60
|
+
bytesLoaded: 0,
|
|
61
|
+
bytesTotal: 0,
|
|
62
|
+
};
|
|
41
63
|
|
|
42
64
|
/**
|
|
43
65
|
* A check that never comes back costs a pass; a suggestion that never comes
|
|
@@ -62,6 +84,15 @@ export const openSpellProvider = (
|
|
|
62
84
|
};
|
|
63
85
|
|
|
64
86
|
port.listen((message) => {
|
|
87
|
+
if (message.type === "opening") {
|
|
88
|
+
publish({
|
|
89
|
+
state: "opening",
|
|
90
|
+
language,
|
|
91
|
+
bytesLoaded: message.bytesLoaded,
|
|
92
|
+
bytesTotal: message.bytesTotal,
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
65
96
|
if (message.type === "ready") {
|
|
66
97
|
publish({ state: "ready", language });
|
|
67
98
|
return;
|
|
@@ -87,20 +118,22 @@ export const openSpellProvider = (
|
|
|
87
118
|
});
|
|
88
119
|
return;
|
|
89
120
|
}
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
121
|
+
const waiting = pending.get(message.requestId);
|
|
122
|
+
if (!waiting) return;
|
|
92
123
|
pending.delete(message.requestId);
|
|
93
|
-
settle({
|
|
124
|
+
waiting.settle({
|
|
94
125
|
requestId: message.requestId,
|
|
95
126
|
revision: message.revision,
|
|
96
127
|
findings: message.findings,
|
|
97
128
|
});
|
|
98
129
|
});
|
|
99
|
-
|
|
100
|
-
|
|
130
|
+
const stopped = (detail: string): void => {
|
|
131
|
+
if (status.state !== "failed")
|
|
132
|
+
publish({ state: "failed", language, reason: "worker", detail });
|
|
101
133
|
abandonSuggestions(detail);
|
|
102
|
-
}
|
|
103
|
-
port.
|
|
134
|
+
};
|
|
135
|
+
port.fail(stopped);
|
|
136
|
+
port.post({ type: "open", language, base, bytesExpected });
|
|
104
137
|
|
|
105
138
|
return {
|
|
106
139
|
language,
|
|
@@ -113,7 +146,27 @@ export const openSpellProvider = (
|
|
|
113
146
|
},
|
|
114
147
|
check: (request) =>
|
|
115
148
|
new Promise((resolve) => {
|
|
116
|
-
|
|
149
|
+
const deadline = setTimeout(() => {
|
|
150
|
+
pending.delete(request.requestId);
|
|
151
|
+
stopped(
|
|
152
|
+
`the ${language} checker did not answer within ${CHECK_DEADLINE_MS}ms`,
|
|
153
|
+
);
|
|
154
|
+
// An empty answer rather than a promise nobody settles: the pass is
|
|
155
|
+
// over, and the marks it would have painted are the ones the failed
|
|
156
|
+
// status has just cleared.
|
|
157
|
+
resolve({
|
|
158
|
+
requestId: request.requestId,
|
|
159
|
+
revision: request.revision,
|
|
160
|
+
findings: [],
|
|
161
|
+
});
|
|
162
|
+
}, CHECK_DEADLINE_MS);
|
|
163
|
+
pending.set(request.requestId, {
|
|
164
|
+
settle: (response) => {
|
|
165
|
+
clearTimeout(deadline);
|
|
166
|
+
resolve(response);
|
|
167
|
+
},
|
|
168
|
+
drop: () => clearTimeout(deadline),
|
|
169
|
+
});
|
|
117
170
|
port.post({ type: "check", ...request });
|
|
118
171
|
}),
|
|
119
172
|
suggest: (request) =>
|
|
@@ -139,6 +192,7 @@ export const openSpellProvider = (
|
|
|
139
192
|
port.post({ type: "suggest", ...request });
|
|
140
193
|
}),
|
|
141
194
|
close: () => {
|
|
195
|
+
for (const waiting of pending.values()) waiting.drop();
|
|
142
196
|
pending.clear();
|
|
143
197
|
abandonSuggestions("the checker closed");
|
|
144
198
|
listeners.clear();
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The tokeniser. A provider is handed paragraph text and decides for itself
|
|
3
|
+
* where the words are, so this is the one place that answers what a word is —
|
|
4
|
+
* and the session ignore list keys off the same normalisation, so ignoring a
|
|
5
|
+
* word at the start of a sentence ignores it in the middle of the next one.
|
|
6
6
|
*/
|
|
7
|
-
import type { LanguageTag } from "./rich-text-spellcheck.js";
|
|
8
7
|
|
|
9
8
|
export interface WordRange {
|
|
10
9
|
readonly start: number;
|
|
@@ -14,365 +13,28 @@ export interface WordRange {
|
|
|
14
13
|
/** What a correction menu shows, and what the engine is asked for. */
|
|
15
14
|
export const SUGGESTION_LIMIT = 5;
|
|
16
15
|
|
|
17
|
-
const ENGLISH: ReadonlySet<string> = new Set([
|
|
18
|
-
"a",
|
|
19
|
-
"about",
|
|
20
|
-
"address",
|
|
21
|
-
"after",
|
|
22
|
-
"afternoon",
|
|
23
|
-
"again",
|
|
24
|
-
"agenda",
|
|
25
|
-
"all",
|
|
26
|
-
"already",
|
|
27
|
-
"also",
|
|
28
|
-
"always",
|
|
29
|
-
"an",
|
|
30
|
-
"and",
|
|
31
|
-
"answer",
|
|
32
|
-
"any",
|
|
33
|
-
"apologies",
|
|
34
|
-
"approved",
|
|
35
|
-
"are",
|
|
36
|
-
"as",
|
|
37
|
-
"at",
|
|
38
|
-
"attached",
|
|
39
|
-
"available",
|
|
40
|
-
"be",
|
|
41
|
-
"because",
|
|
42
|
-
"been",
|
|
43
|
-
"before",
|
|
44
|
-
"both",
|
|
45
|
-
"budget",
|
|
46
|
-
"but",
|
|
47
|
-
"by",
|
|
48
|
-
"calendar",
|
|
49
|
-
"call",
|
|
50
|
-
"can",
|
|
51
|
-
"change",
|
|
52
|
-
"client",
|
|
53
|
-
"colleague",
|
|
54
|
-
"come",
|
|
55
|
-
"confirm",
|
|
56
|
-
"could",
|
|
57
|
-
"customer",
|
|
58
|
-
"day",
|
|
59
|
-
"deadline",
|
|
60
|
-
"decision",
|
|
61
|
-
"definitely",
|
|
62
|
-
"delivery",
|
|
63
|
-
"did",
|
|
64
|
-
"do",
|
|
65
|
-
"document",
|
|
66
|
-
"does",
|
|
67
|
-
"done",
|
|
68
|
-
"down",
|
|
69
|
-
"draft",
|
|
70
|
-
"each",
|
|
71
|
-
"email",
|
|
72
|
-
"end",
|
|
73
|
-
"environment",
|
|
74
|
-
"even",
|
|
75
|
-
"every",
|
|
76
|
-
"few",
|
|
77
|
-
"figures",
|
|
78
|
-
"find",
|
|
79
|
-
"first",
|
|
80
|
-
"follow",
|
|
81
|
-
"for",
|
|
82
|
-
"forward",
|
|
83
|
-
"friday",
|
|
84
|
-
"from",
|
|
85
|
-
"get",
|
|
86
|
-
"give",
|
|
87
|
-
"go",
|
|
88
|
-
"good",
|
|
89
|
-
"had",
|
|
90
|
-
"has",
|
|
91
|
-
"have",
|
|
92
|
-
"he",
|
|
93
|
-
"help",
|
|
94
|
-
"her",
|
|
95
|
-
"here",
|
|
96
|
-
"him",
|
|
97
|
-
"his",
|
|
98
|
-
"how",
|
|
99
|
-
"i",
|
|
100
|
-
"if",
|
|
101
|
-
"in",
|
|
102
|
-
"into",
|
|
103
|
-
"invoice",
|
|
104
|
-
"is",
|
|
105
|
-
"it",
|
|
106
|
-
"its",
|
|
107
|
-
"just",
|
|
108
|
-
"keep",
|
|
109
|
-
"know",
|
|
110
|
-
"last",
|
|
111
|
-
"let",
|
|
112
|
-
"like",
|
|
113
|
-
"long",
|
|
114
|
-
"look",
|
|
115
|
-
"made",
|
|
116
|
-
"make",
|
|
117
|
-
"many",
|
|
118
|
-
"may",
|
|
119
|
-
"me",
|
|
120
|
-
"meeting",
|
|
121
|
-
"message",
|
|
122
|
-
"month",
|
|
123
|
-
"more",
|
|
124
|
-
"morning",
|
|
125
|
-
"most",
|
|
126
|
-
"much",
|
|
127
|
-
"must",
|
|
128
|
-
"my",
|
|
129
|
-
"necessary",
|
|
130
|
-
"need",
|
|
131
|
-
"new",
|
|
132
|
-
"next",
|
|
133
|
-
"no",
|
|
134
|
-
"not",
|
|
135
|
-
"note",
|
|
136
|
-
"notes",
|
|
137
|
-
"now",
|
|
138
|
-
"number",
|
|
139
|
-
"numbers",
|
|
140
|
-
"occurred",
|
|
141
|
-
"of",
|
|
142
|
-
"off",
|
|
143
|
-
"office",
|
|
144
|
-
"on",
|
|
145
|
-
"once",
|
|
146
|
-
"one",
|
|
147
|
-
"only",
|
|
148
|
-
"or",
|
|
149
|
-
"other",
|
|
150
|
-
"our",
|
|
151
|
-
"out",
|
|
152
|
-
"over",
|
|
153
|
-
"own",
|
|
154
|
-
"page",
|
|
155
|
-
"payment",
|
|
156
|
-
"people",
|
|
157
|
-
"please",
|
|
158
|
-
"presentation",
|
|
159
|
-
"price",
|
|
160
|
-
"process",
|
|
161
|
-
"product",
|
|
162
|
-
"project",
|
|
163
|
-
"put",
|
|
164
|
-
"quarter",
|
|
165
|
-
"question",
|
|
166
|
-
"read",
|
|
167
|
-
"ready",
|
|
168
|
-
"receipt",
|
|
169
|
-
"receive",
|
|
170
|
-
"received",
|
|
171
|
-
"recommend",
|
|
172
|
-
"regards",
|
|
173
|
-
"report",
|
|
174
|
-
"review",
|
|
175
|
-
"right",
|
|
176
|
-
"same",
|
|
177
|
-
"say",
|
|
178
|
-
"schedule",
|
|
179
|
-
"see",
|
|
180
|
-
"send",
|
|
181
|
-
"sent",
|
|
182
|
-
"separate",
|
|
183
|
-
"service",
|
|
184
|
-
"she",
|
|
185
|
-
"should",
|
|
186
|
-
"show",
|
|
187
|
-
"since",
|
|
188
|
-
"so",
|
|
189
|
-
"some",
|
|
190
|
-
"sorry",
|
|
191
|
-
"still",
|
|
192
|
-
"such",
|
|
193
|
-
"summary",
|
|
194
|
-
"support",
|
|
195
|
-
"take",
|
|
196
|
-
"team",
|
|
197
|
-
"tell",
|
|
198
|
-
"than",
|
|
199
|
-
"thanks",
|
|
200
|
-
"that",
|
|
201
|
-
"the",
|
|
202
|
-
"their",
|
|
203
|
-
"them",
|
|
204
|
-
"then",
|
|
205
|
-
"there",
|
|
206
|
-
"these",
|
|
207
|
-
"they",
|
|
208
|
-
"thing",
|
|
209
|
-
"think",
|
|
210
|
-
"this",
|
|
211
|
-
"those",
|
|
212
|
-
"through",
|
|
213
|
-
"time",
|
|
214
|
-
"to",
|
|
215
|
-
"today",
|
|
216
|
-
"together",
|
|
217
|
-
"tomorrow",
|
|
218
|
-
"too",
|
|
219
|
-
"two",
|
|
220
|
-
"up",
|
|
221
|
-
"update",
|
|
222
|
-
"us",
|
|
223
|
-
"use",
|
|
224
|
-
"version",
|
|
225
|
-
"very",
|
|
226
|
-
"want",
|
|
227
|
-
"was",
|
|
228
|
-
"way",
|
|
229
|
-
"we",
|
|
230
|
-
"week",
|
|
231
|
-
"welcome",
|
|
232
|
-
"well",
|
|
233
|
-
"were",
|
|
234
|
-
"what",
|
|
235
|
-
"when",
|
|
236
|
-
"where",
|
|
237
|
-
"which",
|
|
238
|
-
"while",
|
|
239
|
-
"who",
|
|
240
|
-
"why",
|
|
241
|
-
"will",
|
|
242
|
-
"with",
|
|
243
|
-
"word",
|
|
244
|
-
"work",
|
|
245
|
-
"would",
|
|
246
|
-
"year",
|
|
247
|
-
"yes",
|
|
248
|
-
"you",
|
|
249
|
-
"your",
|
|
250
|
-
]);
|
|
251
|
-
|
|
252
|
-
const DICTIONARIES: ReadonlyMap<string, ReadonlySet<string>> = new Map([
|
|
253
|
-
["en", ENGLISH],
|
|
254
|
-
]);
|
|
255
|
-
|
|
256
|
-
export const dictionaryFor = (
|
|
257
|
-
language: LanguageTag,
|
|
258
|
-
): ReadonlySet<string> | null =>
|
|
259
|
-
DICTIONARIES.get(language.toLowerCase().split("-")[0]) ?? null;
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* The form a word is looked up and remembered under. The session's ignore list
|
|
263
|
-
* uses it too, so ignoring a word at the start of a sentence ignores it in the
|
|
264
|
-
* middle of the next one.
|
|
265
|
-
*/
|
|
266
16
|
export const normaliseWord = (word: string): string =>
|
|
267
17
|
word.toLowerCase().replace(/’/g, "'");
|
|
268
18
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Every word in the text, in order. Single letters are not offered: they are
|
|
21
|
+
* initials and list markers far more often than they are misspellings.
|
|
22
|
+
*/
|
|
23
|
+
export const wordsIn = (text: string): readonly WordRange[] => {
|
|
273
24
|
const pattern = /\p{L}[\p{L}\p{M}'’]*/gu;
|
|
274
25
|
const found: WordRange[] = [];
|
|
275
26
|
let match = pattern.exec(text);
|
|
276
27
|
while (match) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
found.push({ start: match.index, end: match.index + word.length });
|
|
28
|
+
if (match[0].length > 1) {
|
|
29
|
+
found.push({ start: match.index, end: match.index + match[0].length });
|
|
280
30
|
}
|
|
281
31
|
match = pattern.exec(text);
|
|
282
32
|
}
|
|
283
33
|
return found;
|
|
284
34
|
};
|
|
285
35
|
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
* abandoned once every cell in one is further away than a suggestion may be.
|
|
292
|
-
*/
|
|
293
|
-
const editDistance = (word: string, candidate: string): number => {
|
|
294
|
-
let twoBack: number[] = [];
|
|
295
|
-
let previous = Array.from({ length: candidate.length + 1 }, (_, at) => at);
|
|
296
|
-
for (let row = 1; row <= word.length; row += 1) {
|
|
297
|
-
const current = [row];
|
|
298
|
-
let best = row;
|
|
299
|
-
for (let column = 1; column <= candidate.length; column += 1) {
|
|
300
|
-
const cost = word[row - 1] === candidate[column - 1] ? 0 : 1;
|
|
301
|
-
let step = Math.min(
|
|
302
|
-
current[column - 1] + 1,
|
|
303
|
-
previous[column] + 1,
|
|
304
|
-
previous[column - 1] + cost,
|
|
305
|
-
);
|
|
306
|
-
if (
|
|
307
|
-
row > 1 &&
|
|
308
|
-
column > 1 &&
|
|
309
|
-
word[row - 1] === candidate[column - 2] &&
|
|
310
|
-
word[row - 2] === candidate[column - 1]
|
|
311
|
-
) {
|
|
312
|
-
step = Math.min(step, twoBack[column - 2] + 1);
|
|
313
|
-
}
|
|
314
|
-
current[column] = step;
|
|
315
|
-
if (step < best) best = step;
|
|
316
|
-
}
|
|
317
|
-
if (best > NEAR_ENOUGH) return NEAR_ENOUGH + 1;
|
|
318
|
-
twoBack = previous;
|
|
319
|
-
previous = current;
|
|
320
|
-
}
|
|
321
|
-
return previous[candidate.length];
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
const sharedPrefix = (word: string, candidate: string): number => {
|
|
325
|
-
let shared = 0;
|
|
326
|
-
while (
|
|
327
|
-
shared < word.length &&
|
|
328
|
-
shared < candidate.length &&
|
|
329
|
-
word[shared] === candidate[shared]
|
|
330
|
-
) {
|
|
331
|
-
shared += 1;
|
|
332
|
-
}
|
|
333
|
-
return shared;
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
/** A suggestion arrives dressed the way the word it replaces was written. */
|
|
337
|
-
const wearingTheCaseOf = (word: string, suggestion: string): string => {
|
|
338
|
-
const upper = word.toUpperCase();
|
|
339
|
-
if (word === upper && word !== word.toLowerCase())
|
|
340
|
-
return suggestion.toUpperCase();
|
|
341
|
-
if (word[0] === upper[0] && word[0] !== word.toLowerCase()[0]) {
|
|
342
|
-
return suggestion[0].toUpperCase() + suggestion.slice(1);
|
|
343
|
-
}
|
|
344
|
-
return suggestion;
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* The corrections a word list can offer without an engine: everything within a
|
|
349
|
-
* couple of keystrokes, nearest first, and the one that starts the same way
|
|
350
|
-
* ahead of the one that does not.
|
|
351
|
-
*/
|
|
352
|
-
export const suggestionsFor = (
|
|
353
|
-
word: string,
|
|
354
|
-
words: ReadonlySet<string>,
|
|
355
|
-
): readonly string[] => {
|
|
356
|
-
const target = normaliseWord(word);
|
|
357
|
-
if (target.length < 2 || words.has(target)) return [];
|
|
358
|
-
const ranked: { candidate: string; distance: number; prefix: number }[] = [];
|
|
359
|
-
for (const candidate of words) {
|
|
360
|
-
if (Math.abs(candidate.length - target.length) > NEAR_ENOUGH) continue;
|
|
361
|
-
const distance = editDistance(target, candidate);
|
|
362
|
-
if (distance > NEAR_ENOUGH) continue;
|
|
363
|
-
ranked.push({
|
|
364
|
-
candidate,
|
|
365
|
-
distance,
|
|
366
|
-
prefix: sharedPrefix(target, candidate),
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
ranked.sort(
|
|
370
|
-
(left, right) =>
|
|
371
|
-
left.distance - right.distance ||
|
|
372
|
-
right.prefix - left.prefix ||
|
|
373
|
-
left.candidate.localeCompare(right.candidate),
|
|
374
|
-
);
|
|
375
|
-
return ranked
|
|
376
|
-
.slice(0, SUGGESTION_LIMIT)
|
|
377
|
-
.map(({ candidate }) => wearingTheCaseOf(word, candidate));
|
|
378
|
-
};
|
|
36
|
+
export const findMisspellings = (
|
|
37
|
+
text: string,
|
|
38
|
+
known: (word: string) => boolean,
|
|
39
|
+
): readonly WordRange[] =>
|
|
40
|
+
wordsIn(text).filter((range) => !known(text.slice(range.start, range.end)));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `spellcheck.provider` a composer hands the editor, over a real worker.
|
|
3
|
-
* A language
|
|
3
|
+
* A language this build carries no dictionary for answers null and no worker
|
|
4
|
+
* ever starts, which is what the editor turns into `unavailable`.
|
|
4
5
|
*
|
|
5
6
|
* Its own module, and deliberately not on the package barrel: a bundler emits
|
|
6
|
-
* the worker chunk for whatever graph reaches this `new URL`, and
|
|
7
|
-
*
|
|
8
|
-
* app build.
|
|
7
|
+
* the worker chunk for whatever graph reaches this `new URL`, and a build that
|
|
8
|
+
* stages no dictionaries has no business carrying that chunk.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type {
|
|
@@ -13,9 +13,13 @@ import type {
|
|
|
13
13
|
SpellProvider,
|
|
14
14
|
SpellWorkerResponse,
|
|
15
15
|
} from "./rich-text-spellcheck.js";
|
|
16
|
+
import {
|
|
17
|
+
dictionaryTagFor,
|
|
18
|
+
spellcheckBase,
|
|
19
|
+
spellcheckBytes,
|
|
20
|
+
} from "./rich-text-spellcheck-languages.js";
|
|
16
21
|
import type { SpellWorkerPort } from "./rich-text-spellcheck-provider.js";
|
|
17
22
|
import { openSpellProvider } from "./rich-text-spellcheck-provider.js";
|
|
18
|
-
import { dictionaryFor } from "./rich-text-spellcheck-words.js";
|
|
19
23
|
|
|
20
24
|
const workerPort = (worker: Worker): SpellWorkerPort => ({
|
|
21
25
|
post: (message) => worker.postMessage(message),
|
|
@@ -33,10 +37,16 @@ const workerPort = (worker: Worker): SpellWorkerPort => ({
|
|
|
33
37
|
export const openSpellcheckWorker = async (
|
|
34
38
|
language: LanguageTag,
|
|
35
39
|
): Promise<SpellProvider | null> => {
|
|
36
|
-
|
|
40
|
+
const tag = dictionaryTagFor(language);
|
|
41
|
+
if (!tag) return null;
|
|
37
42
|
const worker = new Worker(
|
|
38
43
|
new URL("./rich-text-spellcheck-worker.ts", import.meta.url),
|
|
39
44
|
{ type: "module" },
|
|
40
45
|
);
|
|
41
|
-
return openSpellProvider(
|
|
46
|
+
return openSpellProvider(
|
|
47
|
+
tag,
|
|
48
|
+
spellcheckBase(),
|
|
49
|
+
workerPort(worker),
|
|
50
|
+
spellcheckBytes(tag),
|
|
51
|
+
);
|
|
42
52
|
};
|