@inshapardaz/likhari-react 0.1.19-dev.186 → 0.1.19-dev.187
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.map +1 -1
- package/dist/EditorRoot.js +2 -1
- package/dist/components/SpellcheckPanel.js +2 -2
- 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 +14 -2
- package/dist/spellcheck/spellcheckActions.d.ts.map +1 -1
- package/dist/spellcheck/spellcheckActions.js +20 -2
- package/dist/theme/editor.css +7 -0
- package/package.json +1 -1
package/dist/EditorRoot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAY1D,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEjF,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAOjE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAY1D,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEjF,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAOjE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAehF,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMnE,yEAAyE;AACzE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,YAAY,GAAG,KAAK,CAAC;AAEnE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,QAAQ,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAClD;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC;;;;;;;;;;;;;;;OAeG;IACH,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,uGAAuG;IACvG,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3E,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IAClD,iBAAiB,IAAI,OAAO,CAAC;IAC7B,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAQD,eAAO,MAAM,UAAU,uGAwYrB,CAAC"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -28,6 +28,7 @@ import { PoetryResizer } from './blocks/PoetryResizer.js';
|
|
|
28
28
|
import { DraftRestore } from './components/DraftRestore.js';
|
|
29
29
|
import { FindReplaceBar } from './components/FindReplaceBar.js';
|
|
30
30
|
import { SpellcheckPanel } from './components/SpellcheckPanel.js';
|
|
31
|
+
import { SpellHighlightPlugin } from './spellcheck/SpellHighlightPlugin.js';
|
|
31
32
|
import { LeaveDialog } from './components/LeaveDialog.js';
|
|
32
33
|
import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
|
|
33
34
|
import { ImageOptionsContext } from './image/ImageOptionsContext.js';
|
|
@@ -291,7 +292,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
|
|
|
291
292
|
setFindOpen(false);
|
|
292
293
|
}, isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
|
|
293
294
|
? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
|
|
294
|
-
: undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsxs("div", { className: "likhari-canvas-frame", children: [config.findReplace && findOpen && _jsx(FindReplaceBar, { strings: strings, dir: dir, onClose: () => setFindOpen(false) }), config.language.spellCheck && spellOpen && _jsx(SpellcheckPanel, { strings: strings, onClose: () => setSpellOpen(false) }), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) })] }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.pageBreak && _jsx(PageBreakPlugin, {}), config.columns && _jsx(LayoutPlugin, {}), config.footnotes && _jsx(FootnotePlugin, {}), config.poetry.enabled && _jsx(PoetryPlugin, {}), config.poetry.enabled && _jsx(PoetryResizer, {}), config.tables && _jsx(TablePlugin, { hasCellMerge: true, hasTabHandler: true }), config.blocks.horizontalRule && _jsx(HorizontalRulePlugin, {}), _jsx(EditorInstancePlugin, { instanceRef: editorInstanceRef }), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
|
|
295
|
+
: undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsxs("div", { className: "likhari-canvas-frame", children: [config.findReplace && findOpen && _jsx(FindReplaceBar, { strings: strings, dir: dir, onClose: () => setFindOpen(false) }), config.language.spellCheck && spellOpen && _jsx(SpellcheckPanel, { strings: strings, onClose: () => setSpellOpen(false) }), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, spellCheck: false, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) })] }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.pageBreak && _jsx(PageBreakPlugin, {}), config.language.spellCheck && _jsx(SpellHighlightPlugin, {}), config.columns && _jsx(LayoutPlugin, {}), config.footnotes && _jsx(FootnotePlugin, {}), config.poetry.enabled && _jsx(PoetryPlugin, {}), config.poetry.enabled && _jsx(PoetryResizer, {}), config.tables && _jsx(TablePlugin, { hasCellMerge: true, hasTabHandler: true }), config.blocks.horizontalRule && _jsx(HorizontalRulePlugin, {}), _jsx(EditorInstancePlugin, { instanceRef: editorInstanceRef }), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
|
|
295
296
|
handleSave();
|
|
296
297
|
settleLeave(true);
|
|
297
298
|
}, onSaveDraft: () => {
|
|
@@ -29,7 +29,7 @@ export function SpellcheckPanel({ strings, onClose }) {
|
|
|
29
29
|
return;
|
|
30
30
|
setChecking(true);
|
|
31
31
|
const resolved = await speller;
|
|
32
|
-
const found = editor.getEditorState().read(() => $collectMisspellings(resolved));
|
|
32
|
+
const found = editor.getEditorState().read(() => $collectMisspellings({ ltr: resolved, rtl: resolved }));
|
|
33
33
|
setItems(found.map((m) => ({
|
|
34
34
|
...m,
|
|
35
35
|
suggestions: resolved.suggest(m.word).slice(0, SUGGESTIONS_PER_WORD),
|
|
@@ -50,7 +50,7 @@ export function SpellcheckPanel({ strings, onClose }) {
|
|
|
50
50
|
editor.update(() => {
|
|
51
51
|
// The list can be stale if the document changed since it was checked:
|
|
52
52
|
// only replace if the same word is still at the same place.
|
|
53
|
-
const current = $collectMisspellings(resolved).find((m) => m.word === item.word && m.match.anchorKey === item.match.anchorKey && m.match.anchorOffset === item.match.anchorOffset);
|
|
53
|
+
const current = $collectMisspellings({ ltr: resolved, rtl: resolved }).find((m) => m.word === item.word && m.match.anchorKey === item.match.anchorKey && m.match.anchorOffset === item.match.anchorOffset);
|
|
54
54
|
if (current)
|
|
55
55
|
$replaceMatch(current.match, suggestion);
|
|
56
56
|
}, { discrete: true });
|
|
@@ -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
|
+
}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { type FindMatch } from '../find/findReplaceActions.js';
|
|
2
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
|
+
}
|
|
3
14
|
export interface Misspelling {
|
|
4
15
|
word: string;
|
|
5
16
|
match: FindMatch;
|
|
17
|
+
segments: Segment[];
|
|
6
18
|
}
|
|
7
|
-
/** Every word in the document
|
|
8
|
-
export declare function $collectMisspellings(
|
|
19
|
+
/** Every word in the document its block's checker does not recognise. */
|
|
20
|
+
export declare function $collectMisspellings(checkers: Checkers): Misspelling[];
|
|
9
21
|
//# sourceMappingURL=spellcheckActions.d.ts.map
|
|
@@ -1 +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,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,CAAC;
|
|
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"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { $textGroups, $locate } from '../find/findReplaceActions.js';
|
|
2
2
|
import { wordsIn } from './spellDictionaries.js';
|
|
3
|
-
/** Every word in the document
|
|
4
|
-
export function $collectMisspellings(
|
|
3
|
+
/** Every word in the document its block's checker does not recognise. */
|
|
4
|
+
export function $collectMisspellings(checkers) {
|
|
5
5
|
const found = [];
|
|
6
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;
|
|
7
11
|
const text = group.map((node) => node.getTextContent()).join('');
|
|
8
12
|
for (const span of wordsIn(text)) {
|
|
9
13
|
if (speller.correct(span.word))
|
|
@@ -13,8 +17,22 @@ export function $collectMisspellings(speller) {
|
|
|
13
17
|
found.push({
|
|
14
18
|
word: span.word,
|
|
15
19
|
match: { anchorKey: start.key, anchorOffset: start.offset, focusKey: end.key, focusOffset: end.offset },
|
|
20
|
+
segments: segmentsOf(group, span.start, span.end),
|
|
16
21
|
});
|
|
17
22
|
}
|
|
18
23
|
}
|
|
19
24
|
return found;
|
|
20
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
|
+
}
|
package/dist/theme/editor.css
CHANGED
|
@@ -963,3 +963,10 @@
|
|
|
963
963
|
.likhari-spell-suggestion {
|
|
964
964
|
font-size: 12px;
|
|
965
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
|
+
}
|