@inshapardaz/likhari-react 0.1.21 → 0.1.22-dev.216
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 +9 -0
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +14 -4
- package/dist/autocorrect/AutoCorrectPlugin.d.ts +27 -0
- package/dist/autocorrect/AutoCorrectPlugin.d.ts.map +1 -0
- package/dist/autocorrect/AutoCorrectPlugin.js +108 -0
- package/dist/autocorrect/autoCorrectActions.d.ts +35 -0
- package/dist/autocorrect/autoCorrectActions.d.ts.map +1 -0
- package/dist/autocorrect/autoCorrectActions.js +182 -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 +15 -0
- package/dist/components/AutoCorrectPanel.d.ts.map +1 -0
- package/dist/components/AutoCorrectPanel.js +44 -0
- package/dist/components/SpellcheckPanel.d.ts +13 -0
- package/dist/components/SpellcheckPanel.d.ts.map +1 -0
- package/dist/components/SpellcheckPanel.js +81 -0
- package/dist/components/Toolbar.d.ts +7 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +112 -109
- package/dist/find/findReplaceActions.d.ts +13 -0
- package/dist/find/findReplaceActions.d.ts.map +1 -1
- package/dist/find/findReplaceActions.js +2 -2
- package/dist/i18n/strings.d.ts +32 -0
- package/dist/i18n/strings.d.ts.map +1 -1
- package/dist/i18n/strings.js +101 -5
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +8 -0
- package/dist/spellcheck/SpellHighlightPlugin.d.ts.map +1 -0
- package/dist/spellcheck/SpellHighlightPlugin.js +57 -0
- package/dist/spellcheck/spellcheckActions.d.ts +21 -0
- package/dist/spellcheck/spellcheckActions.d.ts.map +1 -0
- package/dist/spellcheck/spellcheckActions.js +38 -0
- 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/theme/editor.css +185 -0
- package/package.json +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
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
|
+
const CORRECT_URDU_CHARACTERS = {
|
|
9
|
+
"آ": ["ﺁ", "ﺂ"],
|
|
10
|
+
"أ": ["ﺃ"],
|
|
11
|
+
"ا": ["ﺍ", "ﺎ"],
|
|
12
|
+
"ب": ["ﺏ", "ﺐ", "ﺑ", "ﺒ"],
|
|
13
|
+
"پ": ["ﭖ", "ﭘ", "ﭙ"],
|
|
14
|
+
"ت": ["ﺕ", "ﺖ", "ﺗ", "ﺘ"],
|
|
15
|
+
"ٹ": ["ﭦ", "ﭧ", "ﭨ", "ﭩ"],
|
|
16
|
+
"ث": ["ﺛ", "ﺜ", "ﺚ"],
|
|
17
|
+
"ج": ["ﺝ", "ﺞ", "ﺟ", "ﺠ"],
|
|
18
|
+
"ح": ["ﺡ", "ﺣ", "ﺤ", "ﺢ"],
|
|
19
|
+
"خ": ["ﺧ", "ﺨ", "ﺦ"],
|
|
20
|
+
"د": ["ﺩ", "ﺪ"],
|
|
21
|
+
"ذ": ["ﺬ", "ﺫ"],
|
|
22
|
+
"ر": ["ﺭ", "ﺮ"],
|
|
23
|
+
"ز": ["ﺯ", "ﺰ"],
|
|
24
|
+
"س": ["ﺱ", "ﺲ", "ﺳ", "ﺴ"],
|
|
25
|
+
"ش": ["ﺵ", "ﺶ", "ﺷ", "ﺸ"],
|
|
26
|
+
"ص": ["ﺹ", "ﺺ", "ﺻ", "ﺼ"],
|
|
27
|
+
"ض": ["ﺽ", "ﺾ", "ﺿ", "ﻀ"],
|
|
28
|
+
"ط": ["ﻃ", "ﻄ"],
|
|
29
|
+
"ظ": ["ﻅ", "ﻇ", "ﻈ"],
|
|
30
|
+
"ع": ["ﻉ", "ﻊ", "ﻋ", "ﻌ"],
|
|
31
|
+
"غ": ["ﻍ", "ﻏ", "ﻐ"],
|
|
32
|
+
"ف": ["ﻑ", "ﻒ", "ﻓ", "ﻔ"],
|
|
33
|
+
"ق": ["ﻕ", "ﻖ", "ﻗ", "ﻘ"],
|
|
34
|
+
"ل": ["ﻝ", "ﻞ", "ﻟ", "ﻠ"],
|
|
35
|
+
"م": ["ﻡ", "ﻢ", "ﻣ", "ﻤ"],
|
|
36
|
+
"ن": ["ﻥ", "ﻦ", "ﻧ", "ﻨ"],
|
|
37
|
+
"چ": ["ﭺ", "ﭻ", "ﭼ", "ﭽ"],
|
|
38
|
+
"ڈ": ["ﮈ", "ﮉ"],
|
|
39
|
+
"ڑ": ["ﮍ", "ﮌ"],
|
|
40
|
+
"ژ": ["ﮋ"],
|
|
41
|
+
"ک": ["ﮎ", "ﮏ", "ﮐ", "ﮑ", "ﻛ", "ك"],
|
|
42
|
+
"گ": ["ﮒ", "ﮓ", "ﮔ", "ﮕ"],
|
|
43
|
+
"ں": ["ﮞ", "ﮟ"],
|
|
44
|
+
"و": ["ﻮ", "ﻭ", "ﻮ"],
|
|
45
|
+
"ؤ": ["ﺅ"],
|
|
46
|
+
"ھ": ["ﮪ", "ﮬ", "ﮭ", "ﻬ", "ﻫ", "ﮫ"],
|
|
47
|
+
"ہ": ["ﻩ", "ﮦ", "ﻪ", "ﮧ", "ﮩ", "ﮨ", "ه"],
|
|
48
|
+
"ۂ": [],
|
|
49
|
+
"ۃ": ["ة"],
|
|
50
|
+
"ء": ["ﺀ"],
|
|
51
|
+
"ی": ["ﯼ", "ى", "ﯽ", "ﻰ", "ﻱ", "ﻲ", "ﯾ", "ﯿ", "ي"],
|
|
52
|
+
"ئ": ["ﺋ", "ﺌ"],
|
|
53
|
+
"ے": ["ﮮ", "ﮯ", "ﻳ", "ﻴ"],
|
|
54
|
+
"ۓ": [],
|
|
55
|
+
"۰": ["٠"],
|
|
56
|
+
"۱": ["١"],
|
|
57
|
+
"۲": ["٢"],
|
|
58
|
+
"۳": ["٣"],
|
|
59
|
+
"۴": ["٤"],
|
|
60
|
+
"۵": ["٥"],
|
|
61
|
+
"۶": ["٦"],
|
|
62
|
+
"۷": ["٧"],
|
|
63
|
+
"۸": ["٨"],
|
|
64
|
+
"۹": ["٩"],
|
|
65
|
+
"۔": [],
|
|
66
|
+
"؟": [],
|
|
67
|
+
"٫": [],
|
|
68
|
+
"،": [],
|
|
69
|
+
"لا": ["ﻻ", "ﻼ"],
|
|
70
|
+
"": ["ـ"],
|
|
71
|
+
};
|
|
72
|
+
const TRANSLATOR = new Map();
|
|
73
|
+
for (const [key, values] of Object.entries(CORRECT_URDU_CHARACTERS)) {
|
|
74
|
+
for (const value of values)
|
|
75
|
+
TRANSLATOR.set(value, key);
|
|
76
|
+
}
|
|
77
|
+
/** Combining diacritics (harakat and similar), as urduhack's URDU_DIACRITICS. */
|
|
78
|
+
const DIACRITICS = new Set(['َ', 'ً', 'ٰ', 'ِ', 'ُ', 'ٍ']);
|
|
79
|
+
const ENGLISH_DIGITS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
80
|
+
const URDU_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
|
81
|
+
/** Replaces Arabic and presentation-form letters with standard Urdu letters, character by character. */
|
|
82
|
+
export function normalizeUrduCharacters(text) {
|
|
83
|
+
let out = '';
|
|
84
|
+
for (const char of text)
|
|
85
|
+
out += TRANSLATOR.get(char) ?? char;
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
export function removeUrduDiacritics(text) {
|
|
89
|
+
let out = '';
|
|
90
|
+
for (const char of text)
|
|
91
|
+
if (!DIACRITICS.has(char))
|
|
92
|
+
out += char;
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/** Converts Urdu digits to English digits, or the reverse. */
|
|
96
|
+
export function replaceUrduDigits(text, to) {
|
|
97
|
+
const from = to === 'english' ? URDU_DIGITS : ENGLISH_DIGITS;
|
|
98
|
+
const target = to === 'english' ? ENGLISH_DIGITS : URDU_DIGITS;
|
|
99
|
+
let out = '';
|
|
100
|
+
for (const char of text) {
|
|
101
|
+
const index = from.indexOf(char);
|
|
102
|
+
out += index === -1 ? char : target[index];
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The normalisation urduhack's `normalize` applies, with the diacritic and
|
|
108
|
+
* digit steps made optional. Character mapping always runs.
|
|
109
|
+
*/
|
|
110
|
+
export function normalizeUrdu(text, options = {}) {
|
|
111
|
+
let out = options.removeDiacritics ? removeUrduDiacritics(text) : text;
|
|
112
|
+
out = normalizeUrduCharacters(out);
|
|
113
|
+
if (options.digits === 'english' || options.digits === 'urdu')
|
|
114
|
+
out = replaceUrduDigits(out, options.digits);
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Underlines misspelled words the way the browser does, without changing the
|
|
3
|
+
* document: ranges are set on the rendered text through the CSS Custom
|
|
4
|
+
* Highlight API. Checking is debounced so typing stays smooth. In browsers
|
|
5
|
+
* without the API, nothing is underlined.
|
|
6
|
+
*/
|
|
7
|
+
export declare function SpellHighlightPlugin(): null;
|
|
8
|
+
//# sourceMappingURL=SpellHighlightPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpellHighlightPlugin.d.ts","sourceRoot":"","sources":["../../src/spellcheck/SpellHighlightPlugin.tsx"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,wBAAgB,oBAAoB,SAgDnC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
+
import { $collectMisspellings } from './spellcheckActions.js';
|
|
4
|
+
import { getSpeller } from './spellDictionaries.js';
|
|
5
|
+
/** The CSS Custom Highlight name; editor.css styles it as a red wavy underline. */
|
|
6
|
+
const HIGHLIGHT = 'likhari-spelling';
|
|
7
|
+
const DEBOUNCE_MS = 300;
|
|
8
|
+
/**
|
|
9
|
+
* Underlines misspelled words the way the browser does, without changing the
|
|
10
|
+
* document: ranges are set on the rendered text through the CSS Custom
|
|
11
|
+
* Highlight API. Checking is debounced so typing stays smooth. In browsers
|
|
12
|
+
* without the API, nothing is underlined.
|
|
13
|
+
*/
|
|
14
|
+
export function SpellHighlightPlugin() {
|
|
15
|
+
const [editor] = useLexicalComposerContext();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (typeof CSS === 'undefined' || !('highlights' in CSS))
|
|
18
|
+
return;
|
|
19
|
+
let checkers = null;
|
|
20
|
+
let timer;
|
|
21
|
+
let active = true;
|
|
22
|
+
const paint = () => {
|
|
23
|
+
if (!active || !checkers)
|
|
24
|
+
return;
|
|
25
|
+
const found = editor.getEditorState().read(() => $collectMisspellings(checkers));
|
|
26
|
+
const ranges = [];
|
|
27
|
+
for (const misspelling of found) {
|
|
28
|
+
for (const segment of misspelling.segments) {
|
|
29
|
+
const text = editor.getElementByKey(segment.key)?.firstChild;
|
|
30
|
+
if (!text || text.nodeType !== Node.TEXT_NODE)
|
|
31
|
+
continue;
|
|
32
|
+
const range = document.createRange();
|
|
33
|
+
range.setStart(text, segment.start);
|
|
34
|
+
range.setEnd(text, segment.end);
|
|
35
|
+
ranges.push(range);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
CSS.highlights.set(HIGHLIGHT, new Highlight(...ranges));
|
|
39
|
+
};
|
|
40
|
+
const schedule = () => {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
timer = setTimeout(paint, DEBOUNCE_MS);
|
|
43
|
+
};
|
|
44
|
+
const unregister = editor.registerUpdateListener(schedule);
|
|
45
|
+
Promise.all([getSpeller('en'), getSpeller('ur')]).then(async ([en, ur]) => {
|
|
46
|
+
checkers = { ltr: en ? await en : undefined, rtl: ur ? await ur : undefined };
|
|
47
|
+
schedule();
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
active = false;
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
unregister();
|
|
53
|
+
CSS.highlights.delete(HIGHLIGHT);
|
|
54
|
+
};
|
|
55
|
+
}, [editor]);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type FindMatch } from '../find/findReplaceActions.js';
|
|
2
|
+
import { type Speller } from './spellDictionaries.js';
|
|
3
|
+
/** The checker for each text direction: English for LTR blocks, Urdu for RTL. */
|
|
4
|
+
export interface Checkers {
|
|
5
|
+
ltr?: Speller;
|
|
6
|
+
rtl?: Speller;
|
|
7
|
+
}
|
|
8
|
+
/** The part of a misspelled word that falls inside one text node. */
|
|
9
|
+
export interface Segment {
|
|
10
|
+
key: string;
|
|
11
|
+
start: number;
|
|
12
|
+
end: number;
|
|
13
|
+
}
|
|
14
|
+
export interface Misspelling {
|
|
15
|
+
word: string;
|
|
16
|
+
match: FindMatch;
|
|
17
|
+
segments: Segment[];
|
|
18
|
+
}
|
|
19
|
+
/** Every word in the document its block's checker does not recognise. */
|
|
20
|
+
export declare function $collectMisspellings(checkers: Checkers): Misspelling[];
|
|
21
|
+
//# sourceMappingURL=spellcheckActions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spellcheckActions.d.ts","sourceRoot":"","sources":["../../src/spellcheck/spellcheckActions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAE5D,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qEAAqE;AACrE,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,WAAW,EAAE,CAmBtE"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { $textGroups, $locate } from '../find/findReplaceActions.js';
|
|
2
|
+
import { wordsIn } from './spellDictionaries.js';
|
|
3
|
+
/** Every word in the document its block's checker does not recognise. */
|
|
4
|
+
export function $collectMisspellings(checkers) {
|
|
5
|
+
const found = [];
|
|
6
|
+
for (const group of $textGroups()) {
|
|
7
|
+
const direction = group[0].getParentOrThrow().getDirection();
|
|
8
|
+
const speller = direction === 'rtl' ? checkers.rtl : checkers.ltr;
|
|
9
|
+
if (!speller)
|
|
10
|
+
continue;
|
|
11
|
+
const text = group.map((node) => node.getTextContent()).join('');
|
|
12
|
+
for (const span of wordsIn(text)) {
|
|
13
|
+
if (speller.correct(span.word))
|
|
14
|
+
continue;
|
|
15
|
+
const start = $locate(group, span.start, false);
|
|
16
|
+
const end = $locate(group, span.end, true);
|
|
17
|
+
found.push({
|
|
18
|
+
word: span.word,
|
|
19
|
+
match: { anchorKey: start.key, anchorOffset: start.offset, focusKey: end.key, focusOffset: end.offset },
|
|
20
|
+
segments: segmentsOf(group, span.start, span.end),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return found;
|
|
25
|
+
}
|
|
26
|
+
function segmentsOf(group, start, end) {
|
|
27
|
+
const segments = [];
|
|
28
|
+
let offset = 0;
|
|
29
|
+
for (const node of group) {
|
|
30
|
+
const length = node.getTextContent().length;
|
|
31
|
+
const from = Math.max(start, offset);
|
|
32
|
+
const to = Math.min(end, offset + length);
|
|
33
|
+
if (to > from)
|
|
34
|
+
segments.push({ key: node.getKey(), start: from - offset, end: to - offset });
|
|
35
|
+
offset += length;
|
|
36
|
+
}
|
|
37
|
+
return segments;
|
|
38
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/theme/editor.css
CHANGED
|
@@ -870,3 +870,188 @@
|
|
|
870
870
|
opacity: 0.8;
|
|
871
871
|
white-space: nowrap;
|
|
872
872
|
}
|
|
873
|
+
|
|
874
|
+
/* Spellcheck panel: same top-right floating spot as the find widget, which
|
|
875
|
+
closes when this opens. */
|
|
876
|
+
.likhari-spell-panel {
|
|
877
|
+
position: absolute;
|
|
878
|
+
z-index: 5;
|
|
879
|
+
top: 8px;
|
|
880
|
+
right: 16px;
|
|
881
|
+
width: max-content;
|
|
882
|
+
max-width: min(360px, calc(100% - 32px));
|
|
883
|
+
max-height: 60%;
|
|
884
|
+
overflow-y: auto;
|
|
885
|
+
overflow-x: hidden;
|
|
886
|
+
padding: 8px;
|
|
887
|
+
border: 1px solid var(--editor-line);
|
|
888
|
+
border-radius: 6px;
|
|
889
|
+
background: var(--editor-paper-dim);
|
|
890
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
891
|
+
font-size: 13px;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.likhari-spell-panel[dir='rtl'] {
|
|
895
|
+
right: auto;
|
|
896
|
+
left: 16px;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
.likhari-spell-header,
|
|
900
|
+
.likhari-spell-controls {
|
|
901
|
+
display: flex;
|
|
902
|
+
flex-wrap: wrap;
|
|
903
|
+
align-items: center;
|
|
904
|
+
justify-content: space-between;
|
|
905
|
+
gap: 6px;
|
|
906
|
+
margin-bottom: 6px;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
.likhari-spell-controls select {
|
|
910
|
+
min-width: 0;
|
|
911
|
+
max-width: 100%;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.likhari-spell-panel button {
|
|
915
|
+
padding: 2px 8px;
|
|
916
|
+
border: 1px solid var(--editor-line);
|
|
917
|
+
border-radius: 4px;
|
|
918
|
+
background: transparent;
|
|
919
|
+
color: inherit;
|
|
920
|
+
cursor: pointer;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.likhari-spell-panel button:disabled {
|
|
924
|
+
opacity: 0.5;
|
|
925
|
+
cursor: default;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
.likhari-spell-icon {
|
|
929
|
+
display: inline-flex;
|
|
930
|
+
align-items: center;
|
|
931
|
+
justify-content: center;
|
|
932
|
+
width: 24px;
|
|
933
|
+
height: 24px;
|
|
934
|
+
padding: 0 !important;
|
|
935
|
+
border: none !important;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.likhari-spell-note {
|
|
939
|
+
margin: 4px 0;
|
|
940
|
+
opacity: 0.8;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.likhari-spell-list {
|
|
944
|
+
list-style: none;
|
|
945
|
+
margin: 0;
|
|
946
|
+
padding: 0;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
.likhari-spell-list li {
|
|
950
|
+
display: flex;
|
|
951
|
+
flex-wrap: wrap;
|
|
952
|
+
align-items: center;
|
|
953
|
+
gap: 4px;
|
|
954
|
+
padding: 4px 0;
|
|
955
|
+
border-top: 1px solid var(--editor-line);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.likhari-spell-word {
|
|
959
|
+
font-weight: 600;
|
|
960
|
+
color: #b42318 !important;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
.likhari-spell-suggestion {
|
|
964
|
+
font-size: 12px;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/* Misspelled words, underlined like the browser's own spellcheck. Styled
|
|
968
|
+
through the CSS Custom Highlight API by SpellHighlightPlugin. */
|
|
969
|
+
::highlight(likhari-spelling) {
|
|
970
|
+
text-decoration: red wavy underline;
|
|
971
|
+
text-decoration-skip-ink: none;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.likhari-spell-nav {
|
|
975
|
+
display: flex;
|
|
976
|
+
align-items: center;
|
|
977
|
+
justify-content: flex-end;
|
|
978
|
+
gap: 4px;
|
|
979
|
+
margin-bottom: 6px;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
.likhari-spell-status {
|
|
983
|
+
opacity: 0.8;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.likhari-spell-item {
|
|
987
|
+
display: flex;
|
|
988
|
+
flex-direction: column;
|
|
989
|
+
gap: 6px;
|
|
990
|
+
padding-top: 6px;
|
|
991
|
+
border-top: 1px solid var(--editor-line);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.likhari-spell-suggestions {
|
|
995
|
+
display: flex;
|
|
996
|
+
flex-wrap: wrap;
|
|
997
|
+
gap: 4px;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/* Auto-correct panel: the same floating spot as the spellcheck panel. */
|
|
1001
|
+
.likhari-autocorrect-panel {
|
|
1002
|
+
position: absolute;
|
|
1003
|
+
z-index: 5;
|
|
1004
|
+
top: 8px;
|
|
1005
|
+
right: 16px;
|
|
1006
|
+
width: 260px;
|
|
1007
|
+
max-width: calc(100% - 32px);
|
|
1008
|
+
padding: 8px;
|
|
1009
|
+
border: 1px solid var(--editor-line);
|
|
1010
|
+
border-radius: 6px;
|
|
1011
|
+
background: var(--editor-paper-dim);
|
|
1012
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
1013
|
+
font-size: 13px;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.likhari-autocorrect-panel[dir='rtl'] {
|
|
1017
|
+
right: auto;
|
|
1018
|
+
left: 16px;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
.likhari-autocorrect-field {
|
|
1022
|
+
display: flex;
|
|
1023
|
+
flex-direction: column;
|
|
1024
|
+
gap: 2px;
|
|
1025
|
+
margin-bottom: 6px;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
.likhari-autocorrect-field input,
|
|
1029
|
+
.likhari-autocorrect-field select {
|
|
1030
|
+
padding: 3px 6px;
|
|
1031
|
+
border: 1px solid var(--editor-line);
|
|
1032
|
+
border-radius: 4px;
|
|
1033
|
+
background: var(--editor-paper);
|
|
1034
|
+
color: inherit;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
.likhari-autocorrect-actions {
|
|
1038
|
+
display: flex;
|
|
1039
|
+
flex-direction: column;
|
|
1040
|
+
gap: 6px;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
.likhari-autocorrect-actions button {
|
|
1044
|
+
align-self: flex-start;
|
|
1045
|
+
padding: 2px 8px;
|
|
1046
|
+
border: 1px solid var(--editor-line);
|
|
1047
|
+
border-radius: 4px;
|
|
1048
|
+
background: transparent;
|
|
1049
|
+
color: inherit;
|
|
1050
|
+
cursor: pointer;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.likhari-autocorrect-actions button:disabled {
|
|
1054
|
+
opacity: 0.5;
|
|
1055
|
+
cursor: default;
|
|
1056
|
+
}
|
|
1057
|
+
|