@inshapardaz/likhari-react 0.1.28 → 0.1.29
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 +3 -0
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +2 -2
- package/dist/components/Toolbar.d.ts +9 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +51 -152
- package/dist/i18n/strings.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/spellcheck/SpellHighlightPlugin.d.ts +5 -3
- package/dist/spellcheck/SpellHighlightPlugin.d.ts.map +1 -1
- package/dist/spellcheck/SpellHighlightPlugin.js +164 -28
- package/dist/spellcheck/spellDictionaries.d.ts +2 -0
- package/dist/spellcheck/spellDictionaries.d.ts.map +1 -1
- package/dist/spellcheck/spellDictionaries.js +12 -2
- package/dist/spellcheck/spellcheckActions.d.ts +13 -0
- package/dist/spellcheck/spellcheckActions.d.ts.map +1 -1
- package/dist/spellcheck/spellcheckActions.js +35 -0
- package/dist/theme/editor.css +26 -73
- package/package.json +5 -5
package/dist/EditorRoot.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { MantineThemeOverride } from '@mantine/core';
|
|
|
2
2
|
import type { SerializedEditorState } from 'lexical';
|
|
3
3
|
import { type FormatId } from '@inshapardaz/likhari-converters';
|
|
4
4
|
import { type EditorFeatureConfig, type FeatureConfigPresetName } from '@inshapardaz/likhari-core';
|
|
5
|
+
import { type ToolbarStyle } from './components/Toolbar.js';
|
|
5
6
|
import { type FontOption } from './fonts/index.js';
|
|
6
7
|
import { type DraftRestoreMode } from './components/DraftRestore.js';
|
|
7
8
|
import { type AutoCorrectStore } from './autocorrect/autoCorrectStores.js';
|
|
@@ -47,6 +48,8 @@ export interface EditorRootProps {
|
|
|
47
48
|
* default tokens (packages/core/src/theme/tokens.ts).
|
|
48
49
|
*/
|
|
49
50
|
accentColor?: string;
|
|
51
|
+
/** Toolbar appearance: whether it is outlined, and the button variant. */
|
|
52
|
+
toolbarStyle?: ToolbarStyle;
|
|
50
53
|
locale?: Locale;
|
|
51
54
|
placeholder?: string;
|
|
52
55
|
/**
|
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;
|
|
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;AAIzH,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAOjE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAIhF,OAAO,EAAgC,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACtG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,6BAA6B,CAAC;AACzF,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,EAA4C,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACtG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAYpE,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;AAaD,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mGAAmG;IACnG,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC,sGAAsG;IACtG,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;IACnC,yIAAyI;IACzI,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,mGAAmG;IACnG,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,iHAAiH;IACjH,oBAAoB,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IAC9C,gHAAgH;IAChH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAC7C,wFAAwF;IACxF,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,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,0EAA0E;IAC1E,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,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,uGA2brB,CAAC"}
|
package/dist/EditorRoot.js
CHANGED
|
@@ -58,7 +58,7 @@ function initialEditorStateJson(initialContent) {
|
|
|
58
58
|
const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
|
|
59
59
|
return JSON.stringify(state);
|
|
60
60
|
}
|
|
61
|
-
export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorrectStores, dictionaryStores, completionStores, thesaurusStores, autoCompleteLanguage = 'auto', urduNormalization, punctuation, initialContent, featureConfig, featurePreset, theme, colorScheme, accentColor, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, autosave = true, restoreDraft = 'prompt', navigationGuard = 'confirm', onDraftRestored, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, autosaveMaxBytes = DEFAULT_AUTOSAVE_MAX_BYTES, autosaveMaxDrafts = DEFAULT_AUTOSAVE_MAX_DRAFTS, }, ref) {
|
|
61
|
+
export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorrectStores, dictionaryStores, completionStores, thesaurusStores, autoCompleteLanguage = 'auto', urduNormalization, punctuation, initialContent, featureConfig, featurePreset, theme, colorScheme, accentColor, toolbarStyle, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, autosave = true, restoreDraft = 'prompt', navigationGuard = 'confirm', onDraftRestored, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, autosaveMaxBytes = DEFAULT_AUTOSAVE_MAX_BYTES, autosaveMaxDrafts = DEFAULT_AUTOSAVE_MAX_DRAFTS, }, ref) {
|
|
62
62
|
const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
|
|
63
63
|
const strings = useMemo(() => getStrings(locale), [locale]);
|
|
64
64
|
const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
|
|
@@ -311,7 +311,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, autoCorre
|
|
|
311
311
|
// rather than colliding on a shared one.
|
|
312
312
|
const portalTargetId = useId();
|
|
313
313
|
const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
|
|
314
|
-
return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, accentColor: accentColor, scopeElementId: portalTargetId, children: _jsxs("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: [_jsx("div", { id: portalTargetId, className: "likhari-portal-target" }), _jsx(PortalTargetContext.Provider, { value: portalTargetSelector, children: _jsxs(UiStringsContext.Provider, { value: strings, children: [_jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, dictionaryStores: wordStores, thesaurusStores: thesaurusStores ?? DEFAULT_THESAURUS_STORES, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onSave: handleSave, findOpen: openPanel === 'find', onToggleFind: () => togglePanel('find'), spellOpen: openPanel === 'spell', onToggleSpell: () => togglePanel('spell'), autoCorrectOpen: openPanel === 'autocorrect', onToggleAutoCorrect: () => togglePanel('autocorrect'), isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
|
|
314
|
+
return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, accentColor: accentColor, scopeElementId: portalTargetId, children: _jsxs("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: [_jsx("div", { id: portalTargetId, className: "likhari-portal-target" }), _jsx(PortalTargetContext.Provider, { value: portalTargetSelector, children: _jsxs(UiStringsContext.Provider, { value: strings, children: [_jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, toolbarStyle: toolbarStyle, dictionaryStores: wordStores, thesaurusStores: thesaurusStores ?? DEFAULT_THESAURUS_STORES, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onSave: handleSave, findOpen: openPanel === 'find', onToggleFind: () => togglePanel('find'), spellOpen: openPanel === 'spell', onToggleSpell: () => togglePanel('spell'), autoCorrectOpen: openPanel === 'autocorrect', onToggleAutoCorrect: () => togglePanel('autocorrect'), isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
|
|
315
315
|
? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
|
|
316
316
|
: undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsxs("div", { className: "likhari-canvas-frame", children: [config.findReplace && openPanel === 'find' && _jsx(FindReplaceBar, { strings: strings, dir: dir, onClose: () => setOpenPanel(null) }), config.language.spellCheck && openPanel === 'spell' && (_jsx(SpellcheckPanel, { strings: strings, dir: dir, dictionaryStores: wordStores, onAddAutoCorrect: config.language.autocorrect ? openAutoCorrectFor : undefined, onClose: () => setOpenPanel(null) })), config.language.autocorrect && openPanel === 'autocorrect' && (_jsx(AutoCorrectPanel, { strings: strings, dir: dir, stores: stores, initialFrom: autoCorrectPrefill?.from, initialLanguage: autoCorrectPrefill?.language, onSaved: () => setAutoCorrectVersion((v) => v + 1), onClose: () => setOpenPanel(null) }, autoCorrectPrefill?.id ?? 0)), _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.language.autocomplete && (_jsx(AutocompletePlugin, { label: strings.autoComplete.label, stores: resolvedCompletionStores, language: autoCompleteLanguage })), (config.language.autocorrect || config.language.textCleanup) && _jsx(AutoCorrectPlugin, { stores: stores, version: autoCorrectVersion, enabled: true, typing: config.language.autocorrect, urduNormalization: urduNormalization, punctuation: punctuation }), 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: () => {
|
|
317
317
|
handleSave();
|
|
@@ -5,8 +5,16 @@ import { type DraftsToolbarOptions } from './DraftsDialog.js';
|
|
|
5
5
|
import type { ResolvedEditorFeatureConfig } from '@inshapardaz/likhari-core';
|
|
6
6
|
import { type FontOption } from '../fonts/index.js';
|
|
7
7
|
import type { Locale } from '../i18n/strings.js';
|
|
8
|
+
/** Appearance of the toolbar. The accent colour comes from the editor's `accentColor`. */
|
|
9
|
+
export interface ToolbarStyle {
|
|
10
|
+
/** Outline the toolbar and each button cluster. Default true. */
|
|
11
|
+
bordered?: boolean;
|
|
12
|
+
/** `light` tints the active button, `filled` fills it with the accent. Default `light`. */
|
|
13
|
+
variant?: 'light' | 'filled';
|
|
14
|
+
}
|
|
8
15
|
export interface ToolbarProps {
|
|
9
16
|
config: ResolvedEditorFeatureConfig;
|
|
17
|
+
toolbarStyle?: ToolbarStyle;
|
|
10
18
|
/** Where synonyms come from. */
|
|
11
19
|
thesaurusStores?: ThesaurusStore[];
|
|
12
20
|
/** Where words added to the dictionary are saved. */
|
|
@@ -34,5 +42,5 @@ export interface ToolbarProps {
|
|
|
34
42
|
/** Autosave drafts: when set, the toolbar offers a Drafts button that lists and restores them. */
|
|
35
43
|
drafts?: DraftsToolbarOptions;
|
|
36
44
|
}
|
|
37
|
-
export declare function Toolbar({ config, dictionaryStores, thesaurusStores, onAddAutoCorrect, onSave, isDirty, showSave, findOpen, onToggleFind, spellOpen, onToggleSpell, autoCorrectOpen, onToggleAutoCorrect, fontOptions, direction, locale, drafts }: ToolbarProps): import("react").JSX.Element;
|
|
45
|
+
export declare function Toolbar({ config, toolbarStyle, dictionaryStores, thesaurusStores, onAddAutoCorrect, onSave, isDirty, showSave, findOpen, onToggleFind, spellOpen, onToggleSpell, autoCorrectOpen, onToggleAutoCorrect, fontOptions, direction, locale, drafts }: ToolbarProps): import("react").JSX.Element;
|
|
38
46
|
//# sourceMappingURL=Toolbar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA0CA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAgDtF,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA0CA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAgDtF,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AAggBvD,0FAA0F;AAC1F,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,gCAAgC;IAChC,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;IACnC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IACnE,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,wFAAwF;IACxF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,iGAAiG;IACjG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAqB,EAAE,eAAoB,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAgB,EAAE,YAAY,EAAE,SAAiB,EAAE,aAAa,EAAE,eAAuB,EAAE,mBAAmB,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAugCrV"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { Menu, Select } from '@mantine/core';
|
|
2
|
+
import { Fragment, forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Menu, Select, Toolbar as MantineToolbar } from '@mantine/core';
|
|
4
4
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
5
5
|
import { $getSelection, $isRangeSelection, $isElementNode, $setSelection, $getRoot, $selectAll, $getNearestNodeFromDOMNode, $createTextNode, KEY_MODIFIER_COMMAND, COMMAND_PRIORITY_NORMAL, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND, UNDO_COMMAND, REDO_COMMAND, SELECTION_CHANGE_COMMAND, CAN_UNDO_COMMAND, CAN_REDO_COMMAND, COMMAND_PRIORITY_LOW, } from 'lexical';
|
|
6
6
|
import { $getSelectionStyleValueForProperty, $patchStyleText, $setBlocksType } from '@lexical/selection';
|
|
@@ -108,15 +108,18 @@ function iconOptionRenderer(icons, fallback) {
|
|
|
108
108
|
return (_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx(Icon, { size: 16, stroke: ICON_STROKE }), option.label] }));
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
|
-
function ToolbarButton({ icon: Icon, title, active, dirty,
|
|
112
|
-
return (_jsx(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
const ToolbarButton = forwardRef(function ToolbarButton({ icon: Icon, title, active, dirty, onMouseDown, ...rest }, ref) {
|
|
112
|
+
return (_jsx(MantineToolbar.Toggle, { ref: ref, "aria-label": title, title: title, active: active, "data-dirty": dirty === undefined ? undefined : dirty ? 'true' : 'false',
|
|
113
|
+
// Keep the editor's selection: a mousedown here would blur the canvas.
|
|
114
|
+
onMouseDown: (e) => {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
onMouseDown?.(e);
|
|
117
|
+
}, ...rest, children: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }) }));
|
|
118
|
+
});
|
|
119
|
+
function withDividers(sections) {
|
|
120
|
+
return sections
|
|
121
|
+
.filter((section) => Boolean(section))
|
|
122
|
+
.flatMap((section, index) => (index === 0 ? [section] : [_jsx(MantineToolbar.Divider, {}, `divider-${index}`), section]));
|
|
120
123
|
}
|
|
121
124
|
/** Mantine Select (combobox) for the toolbar's dropdown controls. The icon
|
|
122
125
|
* labels the control itself, since a dropdown can't show one per option in
|
|
@@ -126,24 +129,9 @@ function ToolbarSelect({ icon: Icon, label, value, data, width, disabled, placeh
|
|
|
126
129
|
const portalTarget = usePortalTarget();
|
|
127
130
|
return (_jsx(Select, { size: "xs", w: width, "aria-label": label, title: disabled && comingSoon ? comingSoon(label) : label, data: data, value: value, placeholder: placeholder, disabled: disabled, searchable: searchable, renderOption: renderOption, nothingFoundMessage: searchable ? noMatchMessage : undefined, allowDeselect: false, leftSection: _jsx(Icon, { size: 15, stroke: ICON_STROKE }), comboboxProps: { withinPortal: true, position: 'bottom-start', middlewares: { flip: true, shift: true }, portalProps: { target: portalTarget } }, classNames: { input: 'likhari-toolbar-select-input', dropdown: 'likhari-toolbar-select-dropdown' }, onChange: (v) => v && onChange?.(v) }));
|
|
128
131
|
}
|
|
129
|
-
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
return false;
|
|
133
|
-
for (const v of a)
|
|
134
|
-
if (!b.has(v))
|
|
135
|
-
return false;
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
/** Renders a movable group's items as normal inline toolbar buttons (used
|
|
139
|
-
* both in the real toolbar and in the hidden width-measuring clone). */
|
|
140
|
-
function MovableGroupButtons({ group }) {
|
|
141
|
-
return (_jsx("div", { className: "likhari-toolbar-group", "data-group-key": group.key, children: group.items.map((item) => (_jsx(ToolbarButton, { icon: item.icon, title: item.label, active: item.active, onClick: item.onClick }, item.key))) }));
|
|
142
|
-
}
|
|
143
|
-
function OverflowItem({ icon: Icon, label, active, onClick, }) {
|
|
144
|
-
return (_jsx(Menu.Item, { leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), "data-active": active ? 'true' : 'false', className: "likhari-overflow-item",
|
|
145
|
-
// Keep the editor's selection: a mousedown here would blur the canvas.
|
|
146
|
-
onMouseDown: (e) => e.preventDefault(), onClick: onClick, children: label }));
|
|
132
|
+
/** An item in the "..." menu. A mousedown here would blur the canvas, so it is kept. */
|
|
133
|
+
function MoreMenuItem({ icon: Icon, label, active, onClick, }) {
|
|
134
|
+
return (_jsx(Menu.Item, { leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), "data-active": active ? 'true' : 'false', onMouseDown: (e) => e.preventDefault(), onClick: onClick, children: label }));
|
|
147
135
|
}
|
|
148
136
|
function LinkMenuItems({ url, onEdit, onRemove, strings }) {
|
|
149
137
|
// Only offer the link as clickable if it's a URL the editor would have accepted.
|
|
@@ -186,7 +174,9 @@ function PoetryMenuItems({ strings, layout, scale, centered, onAction, }) {
|
|
|
186
174
|
};
|
|
187
175
|
return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconColumns1, t.singleColumn, setLayout('single'), layout === 'single'), item(IconColumns2, t.twoColumn, setLayout('two-column'), layout === 'two-column'), item(IconArrowsShuffle, t.staggered, setLayout('staggered'), layout === 'staggered'), _jsx(Menu.Divider, {}), _jsx(Menu.Divider, {}), item(IconRowInsertTop, t.insertCoupletBefore, () => $insertCoupletRelativeToSelection('before')), item(IconRowInsertBottom, t.insertCoupletAfter, () => $insertCoupletRelativeToSelection('after')), item(IconArrowsMinimize, t.tighterSpacing, () => $adjustPoetrySpacing(-1), !canStep(POETRY_SPACINGS, scale.spacing, -1)), item(IconArrowsMaximize, t.looserSpacing, () => $adjustPoetrySpacing(1), !canStep(POETRY_SPACINGS, scale.spacing, 1)), layout === 'staggered' && (_jsxs(_Fragment, { children: [item(IconArrowsMinimize, t.narrowerCouplets, () => $adjustPoetryStagger(-1), !canStep(POETRY_STAGGERS, scale.stagger, -1)), item(IconArrowsMaximize, t.widerCouplets, () => $adjustPoetryStagger(1), !canStep(POETRY_STAGGERS, scale.stagger, 1))] })), layout === 'two-column' && (_jsxs(_Fragment, { children: [item(IconAlignCenter, centered ? t.uncenterCouplet : t.centerCouplet, () => $setCoupletCentered(!centered)), item(IconColumns2, t.narrowerGutter, () => $adjustPoetryGutter(-1), !canStep(POETRY_GUTTERS, scale.gutter, -1)), item(IconColumns2, t.widerGutter, () => $adjustPoetryGutter(1), !canStep(POETRY_GUTTERS, scale.gutter, 1))] })), item(IconTrash, t.deleteCouplet, $deletePoetryCouplet, false, 'red'), item(IconTrash, t.deletePoetry, $deletePoetryBlock, false, 'red')] }));
|
|
188
176
|
}
|
|
189
|
-
export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], onAddAutoCorrect, onSave, isDirty, showSave, findOpen = false, onToggleFind, spellOpen = false, onToggleSpell, autoCorrectOpen = false, onToggleAutoCorrect, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
|
|
177
|
+
export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurusStores = [], onAddAutoCorrect, onSave, isDirty, showSave, findOpen = false, onToggleFind, spellOpen = false, onToggleSpell, autoCorrectOpen = false, onToggleAutoCorrect, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
|
|
178
|
+
const bordered = toolbarStyle?.bordered ?? true;
|
|
179
|
+
const variant = toolbarStyle?.variant ?? 'light';
|
|
190
180
|
const [editor] = useLexicalComposerContext();
|
|
191
181
|
const [state, setState] = useState(INITIAL_STATE);
|
|
192
182
|
const strings = useStrings(locale);
|
|
@@ -194,15 +184,6 @@ export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], o
|
|
|
194
184
|
const [draftsOpen, setDraftsOpen] = useState(false);
|
|
195
185
|
const rtl = direction === 'rtl';
|
|
196
186
|
const ALIGN_ICONS = rtl ? ALIGN_ICONS_RTL : ALIGN_ICONS_LTR;
|
|
197
|
-
// Responsive "priority+" overflow: the toolbar's outer container and a
|
|
198
|
-
// hidden nowrap clone (rendered with every movable group forced inline)
|
|
199
|
-
// are both measured; whenever the clone's natural width exceeds the
|
|
200
|
-
// container's available width, the lowest-priority movable groups are
|
|
201
|
-
// moved into the "..." menu, lowest-priority first, until what remains
|
|
202
|
-
// inline fits (or there's nothing left to move).
|
|
203
|
-
const toolbarContainerRef = useRef(null);
|
|
204
|
-
const toolbarMeasureRef = useRef(null);
|
|
205
|
-
const [overflowGroupKeys, setOverflowGroupKeys] = useState(EMPTY_OVERFLOW);
|
|
206
187
|
const updateToolbar = useCallback(() => {
|
|
207
188
|
editor.getEditorState().read(() => {
|
|
208
189
|
const selection = $getSelection();
|
|
@@ -342,7 +323,7 @@ export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], o
|
|
|
342
323
|
};
|
|
343
324
|
const snapshotSelectionRef = useRef(snapshotSelection);
|
|
344
325
|
snapshotSelectionRef.current = snapshotSelection;
|
|
345
|
-
const
|
|
326
|
+
const runMenuItem = (action) => () => {
|
|
346
327
|
const saved = menuSelectionRef.current;
|
|
347
328
|
if (saved)
|
|
348
329
|
editor.update(() => $setSelection(saved.clone()), { discrete: true });
|
|
@@ -646,35 +627,22 @@ export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], o
|
|
|
646
627
|
const headingLevels = config.blocks.headingLevels ?? [];
|
|
647
628
|
const fmt = config.formatting;
|
|
648
629
|
const showFormattingGroup = headingLevels.length > 0 || config.lists.bullet || config.lists.numbered || config.lists.check || config.blocks.quote;
|
|
649
|
-
const showInlineGroup = fmt.bold || fmt.italic || fmt.underline;
|
|
650
630
|
const showAlignGroup = config.alignment.start || config.alignment.center || config.alignment.justify || config.alignment.left || config.alignment.right;
|
|
651
|
-
const
|
|
631
|
+
const showInsertGroup = config.links ||
|
|
632
|
+
config.images.linked ||
|
|
652
633
|
config.images.embedded ||
|
|
653
634
|
config.tables ||
|
|
654
635
|
config.blocks.horizontalRule ||
|
|
655
636
|
config.blocks.pageBreak ||
|
|
656
637
|
config.columns ||
|
|
657
638
|
config.footnotes ||
|
|
658
|
-
config.poetry.enabled
|
|
639
|
+
(config.poetry.enabled && locale !== 'en') ||
|
|
640
|
+
(config.tables && state.inTable) ||
|
|
641
|
+
(config.poetry.enabled && state.inPoetry);
|
|
659
642
|
const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
|
|
660
|
-
|
|
661
|
-
// as normal inline toolbar groups whenever there's room, and only move
|
|
662
|
-
// into the "..." overflow menu when the measured toolbar content doesn't
|
|
663
|
-
// fit the available width (see the ResizeObserver effect below) — this
|
|
664
|
-
// replaces the old always-on overflow menu, which was driven purely by
|
|
665
|
-
// these config flags regardless of actual space.
|
|
666
|
-
const scriptCleanupItems = [];
|
|
667
|
-
if (fmt.strikethrough) {
|
|
668
|
-
scriptCleanupItems.push({
|
|
669
|
-
key: 'strikethrough',
|
|
670
|
-
icon: IconStrikethrough,
|
|
671
|
-
label: strings.toolbar.strikethrough,
|
|
672
|
-
active: state.activeFormats.has('strikethrough'),
|
|
673
|
-
onClick: () => formatText('strikethrough'),
|
|
674
|
-
});
|
|
675
|
-
}
|
|
643
|
+
const moreItems = [];
|
|
676
644
|
if (fmt.superscript) {
|
|
677
|
-
|
|
645
|
+
moreItems.push({
|
|
678
646
|
key: 'superscript',
|
|
679
647
|
icon: IconSuperscript,
|
|
680
648
|
label: strings.toolbar.superscript,
|
|
@@ -683,7 +651,7 @@ export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], o
|
|
|
683
651
|
});
|
|
684
652
|
}
|
|
685
653
|
if (fmt.subscript) {
|
|
686
|
-
|
|
654
|
+
moreItems.push({
|
|
687
655
|
key: 'subscript',
|
|
688
656
|
icon: IconSubscript,
|
|
689
657
|
label: strings.toolbar.subscript,
|
|
@@ -692,10 +660,10 @@ export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], o
|
|
|
692
660
|
});
|
|
693
661
|
}
|
|
694
662
|
if (fmt.caseTransforms) {
|
|
695
|
-
|
|
663
|
+
moreItems.push({ key: 'upper', icon: IconLetterCaseUpper, label: strings.toolbar.uppercase, onClick: () => applyCaseTransform('upper') }, { key: 'lower', icon: IconLetterCaseLower, label: strings.toolbar.lowercase, onClick: () => applyCaseTransform('lower') }, { key: 'capitalize', icon: IconLetterCase, label: strings.toolbar.capitalize, onClick: () => applyCaseTransform('capitalize') });
|
|
696
664
|
}
|
|
697
665
|
if (fmt.clearFormatting) {
|
|
698
|
-
|
|
666
|
+
moreItems.push({ key: 'clear', icon: IconClearFormatting, label: strings.toolbar.clearFormatting, onClick: clearFormatting });
|
|
699
667
|
}
|
|
700
668
|
// UI spec §5: indent/outdent arrows are direction-relative and swap in RTL
|
|
701
669
|
// (outdent moves content toward the start edge, which is the right in RTL).
|
|
@@ -715,56 +683,6 @@ export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], o
|
|
|
715
683
|
},
|
|
716
684
|
]
|
|
717
685
|
: [];
|
|
718
|
-
// Low to high priority: the first group here is the first one pushed into
|
|
719
|
-
// overflow once content stops fitting (UI spec §3.3).
|
|
720
|
-
const movableGroups = [
|
|
721
|
-
{ key: 'scriptCleanup', items: scriptCleanupItems },
|
|
722
|
-
{ key: 'indentOutdent', items: indentItems },
|
|
723
|
-
].filter((g) => g.items.length > 0);
|
|
724
|
-
const movableGroupKeys = useMemo(() => movableGroups.map((g) => g.key).join(','), [movableGroups]);
|
|
725
|
-
// Re-measure whenever the container is resized, or whenever the set of
|
|
726
|
-
// movable groups (and so the hidden clone's content) changes.
|
|
727
|
-
useLayoutEffect(() => {
|
|
728
|
-
const container = toolbarContainerRef.current;
|
|
729
|
-
const measure = toolbarMeasureRef.current;
|
|
730
|
-
if (!container || !measure)
|
|
731
|
-
return;
|
|
732
|
-
// Rough width of the "..." button itself (icon button + its gap) —
|
|
733
|
-
// only charged against the budget once something has actually moved
|
|
734
|
-
// into it, since it isn't rendered at all otherwise.
|
|
735
|
-
const OVERFLOW_BUTTON_WIDTH = 38;
|
|
736
|
-
const recompute = () => {
|
|
737
|
-
const available = container.clientWidth;
|
|
738
|
-
const total = measure.scrollWidth;
|
|
739
|
-
if (total <= available) {
|
|
740
|
-
setOverflowGroupKeys((prev) => (prev.size === 0 ? prev : EMPTY_OVERFLOW));
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
const widths = new Map();
|
|
744
|
-
measure.querySelectorAll('[data-group-key]').forEach((el) => {
|
|
745
|
-
widths.set(el.dataset.groupKey, el.getBoundingClientRect().width);
|
|
746
|
-
});
|
|
747
|
-
let remaining = total;
|
|
748
|
-
const next = new Set();
|
|
749
|
-
for (const key of movableGroupKeys.split(',').filter(Boolean)) {
|
|
750
|
-
if (remaining <= available)
|
|
751
|
-
break;
|
|
752
|
-
const w = widths.get(key);
|
|
753
|
-
if (w === undefined)
|
|
754
|
-
continue;
|
|
755
|
-
remaining -= w;
|
|
756
|
-
next.add(key);
|
|
757
|
-
}
|
|
758
|
-
if (next.size > 0)
|
|
759
|
-
remaining += OVERFLOW_BUTTON_WIDTH;
|
|
760
|
-
setOverflowGroupKeys((prev) => (setsEqual(prev, next) ? prev : next));
|
|
761
|
-
};
|
|
762
|
-
recompute();
|
|
763
|
-
const ro = new ResizeObserver(recompute);
|
|
764
|
-
ro.observe(container);
|
|
765
|
-
ro.observe(measure);
|
|
766
|
-
return () => ro.disconnect();
|
|
767
|
-
});
|
|
768
686
|
// Grouped for the dropdown when options declare groups (Latin / Urdu).
|
|
769
687
|
const fontData = (() => {
|
|
770
688
|
const data = [];
|
|
@@ -807,36 +725,26 @@ export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], o
|
|
|
807
725
|
];
|
|
808
726
|
const formattingValue = state.listType ?? state.blockType;
|
|
809
727
|
const AlignIcon = ALIGN_ICONS[state.elementFormat] ?? IconAlignLeft;
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
openLinkDialog();
|
|
829
|
-
}, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
|
|
830
|
-
showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.blocks.pageBreak && (_jsx(ToolbarButton, { icon: IconPageBreak, title: strings.toolbar.insertPageBreak, onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK_COMMAND, undefined) })), config.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), config.columns && _jsx(ToolbarButton, { icon: IconColumns, title: strings.toolbar.insertColumns, onClick: openLayoutDialog }), config.footnotes && (_jsx(ToolbarButton, { icon: IconNumber1Small, title: strings.toolbar.insertFootnote, onClick: () => editor.dispatchCommand(INSERT_FOOTNOTE_COMMAND, undefined) })), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.findReplace && (_jsx(ToolbarButton, { icon: IconSearch, title: strings.findReplace.toggle, active: findOpen, onClick: onToggleFind })), config.poetry.enabled && locale !== 'en' && (_jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.insertPoetryCouplet, onClick: () => editor.dispatchCommand(INSERT_POETRY_COUPLET_COMMAND, { layout: config.poetry.defaultLayout }) }))] }, "stubInsert")),
|
|
831
|
-
config.tables && state.inTable && (_jsx("div", { className: "likhari-toolbar-group", children: _jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label": strings.toolbar.tableOptions, title: strings.toolbar.tableOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconTableOptions, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: state.tableRows, columns: state.tableColumns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }) })] }) }, "tableActions")),
|
|
832
|
-
config.poetry.enabled && state.inPoetry && (_jsx("div", { className: "likhari-toolbar-group", children: _jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 200, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label": strings.toolbar.poetryOptions, title: strings.toolbar.poetryOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconFeather, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }) })] }) }, "poetryActions")),
|
|
833
|
-
showLanguageGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [config.language.autocorrect && (_jsx(ToolbarButton, { icon: IconWand, title: strings.toolbar.autocorrect, active: autoCorrectOpen, onClick: onToggleAutoCorrect })), config.language.textCleanup && (_jsx(ToolbarButton, { icon: IconSparkles, title: strings.autoCorrect.correctDocument, onClick: () => editor.dispatchCommand(CORRECT_DOCUMENT_COMMAND, undefined) })), config.language.spellCheck && (_jsx(ToolbarButton, { icon: IconAbc, title: strings.toolbar.spellChecker, active: spellOpen, onClick: onToggleSpell }))] }, "language")),
|
|
728
|
+
const formatSection = (showFormattingGroup || fmt.bold || fmt.italic || fmt.underline || fmt.strikethrough || moreItems.length > 0) && (_jsxs(Fragment, { children: [showFormattingGroup && (_jsx(MantineToolbar.Group, { children: _jsx(ToolbarSelect, { icon: IconPilcrow, label: strings.toolbar.formattingLabel, width: 148, value: formattingValue, data: formattingOptions, comingSoon: strings.toolbar.comingSoon, renderOption: iconOptionRenderer(FORMATTING_ICONS, IconPilcrow), onChange: withRefocus((v) => applyFormatting(v)) }) })), (fmt.bold || fmt.italic || fmt.underline || fmt.strikethrough || moreItems.length > 0) && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: [fmt.bold && _jsx(ToolbarButton, { icon: IconBold, title: strings.toolbar.bold, active: state.activeFormats.has('bold'), onClick: () => formatText('bold') }), fmt.italic && (_jsx(ToolbarButton, { icon: IconItalic, title: strings.toolbar.italic, active: state.activeFormats.has('italic'), onClick: () => formatText('italic') })), fmt.underline && (_jsx(ToolbarButton, { icon: IconUnderline, title: strings.toolbar.underline, active: state.activeFormats.has('underline'), onClick: () => formatText('underline') })), fmt.strikethrough && (_jsx(ToolbarButton, { icon: IconStrikethrough, title: strings.toolbar.strikethrough, active: state.activeFormats.has('strikethrough'), onClick: () => formatText('strikethrough') })), moreItems.length > 0 && (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 210, closeOnItemClick: true, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconDots, title: strings.toolbar.moreFormatting }) }), _jsx(Menu.Dropdown, { children: moreItems.map((item) => (_jsx(MoreMenuItem, { icon: item.icon, label: item.label, active: item.active, onClick: runMenuItem(item.onClick) }, item.key))) })] }))] }))] }, "format"));
|
|
729
|
+
const alignSection = (showAlignGroup || indentItems.length > 0) && (_jsxs(Fragment, { children: [showAlignGroup && (_jsx(MantineToolbar.Group, { children: _jsx(ToolbarSelect, { icon: AlignIcon, label: strings.toolbar.alignment, width: 138, value: state.elementFormat || 'start', data: alignOptions, renderOption: iconOptionRenderer(ALIGN_ICONS, IconAlignLeft), onChange: withRefocus((v) => formatElement(v)) }) })), indentItems.length > 0 && (_jsx(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: indentItems.map((item) => (_jsx(ToolbarButton, { icon: item.icon, title: item.label, onClick: item.onClick }, item.key))) }))] }, "align"));
|
|
730
|
+
const fontSection = (config.font.family || config.font.size) && (_jsxs(MantineToolbar.Group, { children: [config.font.family && (_jsx(ToolbarSelect, { icon: IconTypography, label: strings.toolbar.fontFamily, width: 150, value: fontOptions.some((f) => f.family === shownFontFamily) ? shownFontFamily : null, placeholder: strings.toolbar.fontFamilyPlaceholder, data: fontData, searchable: true, noMatchMessage: strings.toolbar.noMatch, renderOption: ({ option }) => _jsx("span", { style: { fontFamily: option.value }, children: option.label }), onChange: withRefocus((v) => applyFont('font-family', v)) })), config.font.size && (_jsx(ToolbarSelect, { icon: IconTextSize, label: strings.toolbar.fontSize, width: 84, value: FONT_SIZES_PX.some((px) => `${px}px` === shownFontSize) ? shownFontSize : null, placeholder: strings.toolbar.fontSizePlaceholder, data: FONT_SIZES_PX.map((px) => ({ value: `${px}px`, label: String(px) })), onChange: withRefocus((v) => applyFont('font-size', v)) }))] }, "font"));
|
|
731
|
+
const insertSection = showInsertGroup && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: [config.links &&
|
|
732
|
+
(state.isLink ? (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.linkOptions, active: true, "aria-haspopup": "menu" }) }), _jsx(Menu.Dropdown, { children: _jsx(LinkMenuItems, { url: state.linkUrl, onEdit: () => {
|
|
733
|
+
restoreSelection();
|
|
734
|
+
openLinkDialog();
|
|
735
|
+
}, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog }))), (config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), config.tables && state.inTable && (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconTableOptions, title: strings.toolbar.tableOptions, active: true, "aria-haspopup": "menu" }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: state.tableRows, columns: state.tableColumns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }) })] })), config.columns && _jsx(ToolbarButton, { icon: IconColumns, title: strings.toolbar.insertColumns, onClick: openLayoutDialog }), config.footnotes && (_jsx(ToolbarButton, { icon: IconNumber1Small, title: strings.toolbar.insertFootnote, onClick: () => editor.dispatchCommand(INSERT_FOOTNOTE_COMMAND, undefined) })), config.blocks.pageBreak && (_jsx(ToolbarButton, { icon: IconPageBreak, title: strings.toolbar.insertPageBreak, onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK_COMMAND, undefined) })), config.poetry.enabled && locale !== 'en' && (_jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.insertPoetryCouplet, onClick: () => editor.dispatchCommand(INSERT_POETRY_COUPLET_COMMAND, { layout: config.poetry.defaultLayout }) })), config.poetry.enabled && state.inPoetry && (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 200, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.poetryOptions, active: true, "aria-haspopup": "menu" }) }), _jsx(Menu.Dropdown, { children: _jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }) })] }))] }, "insert"));
|
|
736
|
+
const toolsSection = showLanguageGroup && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: [config.language.autocorrect && (_jsx(ToolbarButton, { icon: IconWand, title: strings.toolbar.autocorrect, active: autoCorrectOpen, onClick: onToggleAutoCorrect })), config.language.textCleanup && (_jsx(ToolbarButton, { icon: IconSparkles, title: strings.autoCorrect.correctDocument, onClick: () => editor.dispatchCommand(CORRECT_DOCUMENT_COMMAND, undefined) })), config.language.spellCheck && (_jsx(ToolbarButton, { icon: IconAbc, title: strings.toolbar.spellChecker, active: spellOpen, onClick: onToggleSpell }))] }, "tools"));
|
|
737
|
+
const endSection = (drafts || config.findReplace) && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", style: { marginInlineStart: 'auto' }, children: [drafts && _jsx(ToolbarButton, { icon: IconHistory, title: strings.toolbar.drafts, onClick: () => setDraftsOpen(true) }), config.findReplace && (_jsx(ToolbarButton, { icon: IconSearch, title: strings.findReplace.toggle, active: findOpen, onClick: onToggleFind }))] }, "end"));
|
|
738
|
+
const startSections = [
|
|
739
|
+
showSave && (_jsx(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: _jsx(ToolbarButton, { icon: IconDeviceFloppy, title: strings.toolbar.save, dirty: Boolean(isDirty), onClick: onSave }) }, "save")),
|
|
740
|
+
config.history && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: [_jsx(ToolbarButton, { icon: IconArrowBackUp, title: strings.toolbar.undo, disabled: !state.canUndo, onClick: () => editor.dispatchCommand(UNDO_COMMAND, undefined) }), _jsx(ToolbarButton, { icon: IconArrowForwardUp, title: strings.toolbar.redo, disabled: !state.canRedo, onClick: () => editor.dispatchCommand(REDO_COMMAND, undefined) })] }, "history")),
|
|
741
|
+
formatSection,
|
|
742
|
+
alignSection,
|
|
743
|
+
fontSection,
|
|
744
|
+
insertSection,
|
|
745
|
+
toolsSection,
|
|
834
746
|
];
|
|
835
|
-
return (_jsxs("
|
|
836
|
-
.filter((group) => !overflowGroupKeys.has(group.key))
|
|
837
|
-
.map((group) => (_jsx(MovableGroupButtons, { group: group }, group.key))), overflowGroupKeys.size > 0 && (_jsxs(Menu, { position: "bottom-end", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 210, closeOnItemClick: true, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "aria-label": strings.toolbar.moreFormatting, title: strings.toolbar.moreFormatting, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconDots, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: movableGroups
|
|
838
|
-
.filter((group) => overflowGroupKeys.has(group.key))
|
|
839
|
-
.flatMap((group) => group.items.map((item) => (_jsx(OverflowItem, { icon: item.icon, label: item.label, active: item.active, onClick: runOverflowAction(item.onClick) }, item.key)))) })] })), _jsxs(Menu, { opened: contextMenu !== null, onChange: (opened) => {
|
|
747
|
+
return (_jsxs(MantineToolbar, { "aria-label": strings.toolbar.ariaLabel, className: bordered ? 'likhari-toolbar' : 'likhari-toolbar likhari-toolbar--plain', withBorder: bordered, variant: variant, color: "var(--editor-accent)", children: [withDividers(startSections), endSection, _jsxs(Menu, { opened: contextMenu !== null, onChange: (opened) => {
|
|
840
748
|
if (!opened)
|
|
841
749
|
setContextMenu(null);
|
|
842
750
|
}, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 260, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: contextMenu?.x ?? -9999, top: contextMenu?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsxs(Menu.Dropdown, { children: [contextMenu?.spelling && (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: strings.contextMenu.spelling }), contextMenu.spelling.suggestions.length === 0 && _jsx(Menu.Item, { disabled: true, children: strings.contextMenu.noSuggestions }), contextMenu.spelling.suggestions.map((suggestion) => (_jsx(Menu.Item, { onClick: () => {
|
|
@@ -864,14 +772,5 @@ export function Toolbar({ config, dictionaryStores = [], thesaurusStores = [], o
|
|
|
864
772
|
}, children: synonym }, synonym))), _jsx(Menu.Divider, {})] })), contextMenu?.link !== undefined && (_jsxs(_Fragment, { children: [_jsx(LinkMenuItems, { url: contextMenu.link, onEdit: () => {
|
|
865
773
|
restoreSelection();
|
|
866
774
|
openLinkDialog();
|
|
867
|
-
}, onRemove: removeLink, strings: strings }), _jsx(Menu.Divider, {})] })), contextMenu?.table && (_jsxs(_Fragment, { children: [_jsx(TableMenuItems, { strings: strings, direction: direction, rows: contextMenu.table.rows, columns: contextMenu.table.columns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }), _jsx(Menu.Divider, {})] })), contextMenu?.poetry && (_jsxs(_Fragment, { children: [_jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }), _jsx(Menu.Divider, {})] })), _jsx(Menu.Item, { leftSection: _jsx(IconCut, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void copyOrCut(true)(), children: strings.contextMenu.cut }), _jsx(Menu.Item, { leftSection: _jsx(IconCopy, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void copyOrCut(false)(), children: strings.contextMenu.copy }), _jsx(Menu.Item, { leftSection: _jsx(IconClipboard, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void paste(), children: strings.contextMenu.paste }), _jsx(Menu.Item, { leftSection: _jsx(IconSelectAll, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: runMenuAction(() => $selectAll()), children: strings.contextMenu.selectAll })] })] }),
|
|
868
|
-
position: 'absolute',
|
|
869
|
-
visibility: 'hidden',
|
|
870
|
-
pointerEvents: 'none',
|
|
871
|
-
top: 0,
|
|
872
|
-
left: 0,
|
|
873
|
-
display: 'flex',
|
|
874
|
-
flexWrap: 'nowrap',
|
|
875
|
-
gap: 8,
|
|
876
|
-
}, children: [fixedGroups, movableGroups.map((group) => (_jsx(MovableGroupButtons, { group: group }, group.key)))] }), (config.images.linked || config.images.embedded) && (_jsx(ImageDialog, { mode: "insert", opened: imageDialogOpen, onSubmit: insertImage, onClose: closeImageDialog })), config.tables && _jsx(TableDialog, { opened: tableDialogOpen, onSubmit: insertTable, onClose: closeTableDialog }), config.columns && _jsx(LayoutDialog, { opened: layoutDialogOpen, onSubmit: insertLayout, onClose: closeLayoutDialog }), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog })), drafts && _jsx(DraftsDialog, { opened: draftsOpen, locale: locale, onClose: () => setDraftsOpen(false), ...drafts })] }));
|
|
775
|
+
}, onRemove: removeLink, strings: strings }), _jsx(Menu.Divider, {})] })), contextMenu?.table && (_jsxs(_Fragment, { children: [_jsx(TableMenuItems, { strings: strings, direction: direction, rows: contextMenu.table.rows, columns: contextMenu.table.columns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }), _jsx(Menu.Divider, {})] })), contextMenu?.poetry && (_jsxs(_Fragment, { children: [_jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }), _jsx(Menu.Divider, {})] })), _jsx(Menu.Item, { leftSection: _jsx(IconCut, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void copyOrCut(true)(), children: strings.contextMenu.cut }), _jsx(Menu.Item, { leftSection: _jsx(IconCopy, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void copyOrCut(false)(), children: strings.contextMenu.copy }), _jsx(Menu.Item, { leftSection: _jsx(IconClipboard, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void paste(), children: strings.contextMenu.paste }), _jsx(Menu.Item, { leftSection: _jsx(IconSelectAll, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: runMenuAction(() => $selectAll()), children: strings.contextMenu.selectAll })] })] }), (config.images.linked || config.images.embedded) && (_jsx(ImageDialog, { mode: "insert", opened: imageDialogOpen, onSubmit: insertImage, onClose: closeImageDialog })), config.tables && _jsx(TableDialog, { opened: tableDialogOpen, onSubmit: insertTable, onClose: closeTableDialog }), config.columns && _jsx(LayoutDialog, { opened: layoutDialogOpen, onSubmit: insertLayout, onClose: closeLayoutDialog }), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog })), drafts && _jsx(DraftsDialog, { opened: draftsOpen, locale: locale, onClose: () => setDraftsOpen(false), ...drafts })] }));
|
|
877
776
|
}
|
package/dist/i18n/strings.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { EditorRoot } from './EditorRoot.js';
|
|
2
2
|
export type { EditorRootProps, EditorRef, EditorInitialContent, NavigationGuardMode } from './EditorRoot.js';
|
|
3
|
+
export type { ToolbarStyle } from './components/Toolbar.js';
|
|
3
4
|
export type { DraftRestoreMode } from './components/DraftRestore.js';
|
|
4
5
|
export { defaultMantineTheme } from './theme/mantineTheme.js';
|
|
5
6
|
export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js';
|
|
@@ -8,7 +9,7 @@ export { DEFAULT_FONT_OPTIONS, URDU_WEB_FONT_OPTIONS, FONT_SIZES_PX } from './fo
|
|
|
8
9
|
export type { FontOption } from './fonts/index.js';
|
|
9
10
|
export { STRINGS, getStrings, useStrings, useUiStrings, UiStringsContext } from './i18n/index.js';
|
|
10
11
|
export type { Locale, Strings } from './i18n/index.js';
|
|
11
|
-
export { registerSpellDictionary, hasSpellDictionary, type HunspellFiles, type SpellLanguage } from './spellcheck/spellDictionaries.js';
|
|
12
|
+
export { registerSpellDictionary, hasSpellDictionary, setEnglishDictionaryBaseUrl, type HunspellFiles, type SpellLanguage } from './spellcheck/spellDictionaries.js';
|
|
12
13
|
export { localStorageAutoCorrectStore, apiAutoCorrectStore, fileAutoCorrectStore } from './autocorrect/autoCorrectStores.js';
|
|
13
14
|
export type { AutoCorrectStore, AutoCorrectEntry } from './autocorrect/autoCorrectStores.js';
|
|
14
15
|
export { normalizeUrdu, normalizeUrduCharacters, removeUrduDiacritics, replaceUrduDigits } from './normalization/urduNormalize.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC1G,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAClK,OAAO,EAAE,4BAA4B,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC1H,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAChI,YAAY,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAClI,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAChG,YAAY,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { defaultMantineTheme } from './theme/mantineTheme.js';
|
|
|
3
3
|
export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js';
|
|
4
4
|
export { DEFAULT_FONT_OPTIONS, URDU_WEB_FONT_OPTIONS, FONT_SIZES_PX } from './fonts/index.js';
|
|
5
5
|
export { STRINGS, getStrings, useStrings, useUiStrings, UiStringsContext } from './i18n/index.js';
|
|
6
|
-
export { registerSpellDictionary, hasSpellDictionary } from './spellcheck/spellDictionaries.js';
|
|
6
|
+
export { registerSpellDictionary, hasSpellDictionary, setEnglishDictionaryBaseUrl } from './spellcheck/spellDictionaries.js';
|
|
7
7
|
export { localStorageAutoCorrectStore, apiAutoCorrectStore, fileAutoCorrectStore } from './autocorrect/autoCorrectStores.js';
|
|
8
8
|
export { normalizeUrdu, normalizeUrduCharacters, removeUrduDiacritics, replaceUrduDigits } from './normalization/urduNormalize.js';
|
|
9
9
|
export { localStorageUserWordStore, apiUserWordStore, ignoreWord, addUserWord, onSpellWordsChange } from './spellcheck/userWords.js';
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Underlines misspelled words the way the browser does, without changing the
|
|
3
|
-
* document
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* document. Only blocks that are in view are checked, a block a few seconds after
|
|
4
|
+
* it scrolls into view, and an edit is rechecked after typing pauses. Results are
|
|
5
|
+
* kept per block, so a change re-checks only the block it is in. Underlines are
|
|
6
|
+
* ranges set through the CSS Custom Highlight API. In browsers without it, nothing
|
|
7
|
+
* is underlined.
|
|
6
8
|
*/
|
|
7
9
|
export declare function SpellHighlightPlugin(): null;
|
|
8
10
|
//# sourceMappingURL=SpellHighlightPlugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpellHighlightPlugin.d.ts","sourceRoot":"","sources":["../../src/spellcheck/SpellHighlightPlugin.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SpellHighlightPlugin.d.ts","sourceRoot":"","sources":["../../src/spellcheck/SpellHighlightPlugin.tsx"],"names":[],"mappings":"AAsBA;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,SAoJnC"}
|
|
@@ -1,60 +1,196 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
3
|
-
import { $
|
|
3
|
+
import { $getNodeByKey, $getRoot, $isElementNode } from 'lexical';
|
|
4
|
+
import { $misspellingsInBlock } from './spellcheckActions.js';
|
|
4
5
|
import { getSpeller } from './spellDictionaries.js';
|
|
5
6
|
import { onSpellWordsChange } from './userWords.js';
|
|
6
7
|
/** The CSS Custom Highlight name; editor.css styles it as a red wavy underline. */
|
|
7
8
|
const HIGHLIGHT = 'likhari-spelling';
|
|
8
|
-
|
|
9
|
+
/** A block that has just come into view is checked after this long, so scrolling past it costs nothing. */
|
|
10
|
+
const VIEW_DELAY_MS = 2000;
|
|
11
|
+
/** An edit inside a visible block is checked after the typing pauses for this long. */
|
|
12
|
+
const TYPING_DELAY_MS = 300;
|
|
13
|
+
/** The top-level block (paragraph, table, list...) that holds a node, or null. */
|
|
14
|
+
function $topLevelKey(node) {
|
|
15
|
+
const root = $getRoot();
|
|
16
|
+
let current = node;
|
|
17
|
+
while (current.getParent() && !current.getParent().is(root))
|
|
18
|
+
current = current.getParent();
|
|
19
|
+
return current.getParent() ? current.getKey() : null;
|
|
20
|
+
}
|
|
9
21
|
/**
|
|
10
22
|
* Underlines misspelled words the way the browser does, without changing the
|
|
11
|
-
* document
|
|
12
|
-
*
|
|
13
|
-
*
|
|
23
|
+
* document. Only blocks that are in view are checked, a block a few seconds after
|
|
24
|
+
* it scrolls into view, and an edit is rechecked after typing pauses. Results are
|
|
25
|
+
* kept per block, so a change re-checks only the block it is in. Underlines are
|
|
26
|
+
* ranges set through the CSS Custom Highlight API. In browsers without it, nothing
|
|
27
|
+
* is underlined.
|
|
14
28
|
*/
|
|
15
29
|
export function SpellHighlightPlugin() {
|
|
16
30
|
const [editor] = useLexicalComposerContext();
|
|
17
31
|
useEffect(() => {
|
|
18
|
-
if (typeof CSS === 'undefined' || !('highlights' in CSS))
|
|
32
|
+
if (typeof CSS === 'undefined' || !('highlights' in CSS) || typeof IntersectionObserver === 'undefined')
|
|
19
33
|
return;
|
|
20
34
|
let checkers = null;
|
|
21
|
-
let timer;
|
|
22
35
|
let active = true;
|
|
36
|
+
const cache = new Map();
|
|
37
|
+
const visible = new Set();
|
|
38
|
+
const elements = new Map();
|
|
39
|
+
const keyOf = new Map();
|
|
40
|
+
const timers = new Map();
|
|
23
41
|
const paint = () => {
|
|
24
|
-
if (!active || !checkers)
|
|
25
|
-
return;
|
|
26
|
-
const found = editor.getEditorState().read(() => $collectMisspellings(checkers));
|
|
27
42
|
const ranges = [];
|
|
28
|
-
for (const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
for (const [key, words] of cache) {
|
|
44
|
+
const block = editor.getElementByKey(key);
|
|
45
|
+
if (!block || words.length === 0)
|
|
46
|
+
continue;
|
|
47
|
+
const pieces = textPieces(block);
|
|
48
|
+
for (const word of words) {
|
|
49
|
+
for (const piece of pieces) {
|
|
50
|
+
const start = Math.max(word.start, piece.start);
|
|
51
|
+
const end = Math.min(word.end, piece.end);
|
|
52
|
+
if (end <= start)
|
|
53
|
+
continue;
|
|
54
|
+
const range = document.createRange();
|
|
55
|
+
range.setStart(piece.node, start - piece.start);
|
|
56
|
+
range.setEnd(piece.node, end - piece.start);
|
|
57
|
+
ranges.push(range);
|
|
58
|
+
}
|
|
37
59
|
}
|
|
38
60
|
}
|
|
39
61
|
CSS.highlights.set(HIGHLIGHT, new Highlight(...ranges));
|
|
40
62
|
};
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
const checkBlock = (key) => {
|
|
64
|
+
if (!active || !checkers)
|
|
65
|
+
return;
|
|
66
|
+
const words = editor.getEditorState().read(() => {
|
|
67
|
+
const node = $getNodeByKey(key);
|
|
68
|
+
return $isElementNode(node) ? $misspellingsInBlock(node, checkers) : null;
|
|
69
|
+
});
|
|
70
|
+
if (words === null)
|
|
71
|
+
cache.delete(key);
|
|
72
|
+
else
|
|
73
|
+
cache.set(key, words);
|
|
74
|
+
paint();
|
|
75
|
+
};
|
|
76
|
+
const schedule = (key, delay) => {
|
|
77
|
+
clearTimeout(timers.get(key));
|
|
78
|
+
timers.set(key, setTimeout(() => {
|
|
79
|
+
timers.delete(key);
|
|
80
|
+
if (visible.has(key))
|
|
81
|
+
checkBlock(key);
|
|
82
|
+
}, delay));
|
|
83
|
+
};
|
|
84
|
+
const observer = new IntersectionObserver((entries) => {
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
const key = keyOf.get(entry.target);
|
|
87
|
+
if (key === undefined)
|
|
88
|
+
continue;
|
|
89
|
+
if (entry.isIntersecting) {
|
|
90
|
+
visible.add(key);
|
|
91
|
+
if (!cache.has(key))
|
|
92
|
+
schedule(key, VIEW_DELAY_MS);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
visible.delete(key);
|
|
96
|
+
clearTimeout(timers.get(key));
|
|
97
|
+
timers.delete(key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// Keeps the observer on exactly the current top-level blocks.
|
|
102
|
+
const syncBlocks = (editorState = editor.getEditorState()) => {
|
|
103
|
+
const current = new Map();
|
|
104
|
+
editorState.read(() => {
|
|
105
|
+
for (const block of $getRoot().getChildren()) {
|
|
106
|
+
const element = editor.getElementByKey(block.getKey());
|
|
107
|
+
if (element)
|
|
108
|
+
current.set(block.getKey(), element);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
for (const [key, element] of elements) {
|
|
112
|
+
if (current.get(key) !== element) {
|
|
113
|
+
observer.unobserve(element);
|
|
114
|
+
keyOf.delete(element);
|
|
115
|
+
elements.delete(key);
|
|
116
|
+
visible.delete(key);
|
|
117
|
+
cache.delete(key);
|
|
118
|
+
clearTimeout(timers.get(key));
|
|
119
|
+
timers.delete(key);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const [key, element] of current) {
|
|
123
|
+
if (!elements.has(key)) {
|
|
124
|
+
elements.set(key, element);
|
|
125
|
+
keyOf.set(element, key);
|
|
126
|
+
observer.observe(element);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
44
129
|
};
|
|
45
|
-
const
|
|
46
|
-
|
|
130
|
+
const unregisterUpdate = editor.registerUpdateListener(({ editorState, dirtyElements, dirtyLeaves }) => {
|
|
131
|
+
if (!checkers)
|
|
132
|
+
return;
|
|
133
|
+
const dirty = new Set();
|
|
134
|
+
editorState.read(() => {
|
|
135
|
+
for (const key of dirtyLeaves) {
|
|
136
|
+
const node = $getNodeByKey(key);
|
|
137
|
+
const top = node ? $topLevelKey(node) : null;
|
|
138
|
+
if (top)
|
|
139
|
+
dirty.add(top);
|
|
140
|
+
}
|
|
141
|
+
for (const key of dirtyElements.keys()) {
|
|
142
|
+
const node = $getNodeByKey(key);
|
|
143
|
+
const top = node ? $topLevelKey(node) : null;
|
|
144
|
+
if (top)
|
|
145
|
+
dirty.add(top);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
syncBlocks(editorState);
|
|
149
|
+
for (const key of dirty) {
|
|
150
|
+
if (visible.has(key))
|
|
151
|
+
schedule(key, TYPING_DELAY_MS);
|
|
152
|
+
else
|
|
153
|
+
cache.delete(key);
|
|
154
|
+
}
|
|
155
|
+
if (dirty.size > 0)
|
|
156
|
+
paint();
|
|
157
|
+
});
|
|
158
|
+
const unsubscribeWords = onSpellWordsChange(() => {
|
|
159
|
+
cache.clear();
|
|
160
|
+
for (const key of visible)
|
|
161
|
+
schedule(key, 0);
|
|
162
|
+
paint();
|
|
163
|
+
});
|
|
47
164
|
Promise.all([getSpeller('en'), getSpeller('ur')]).then(async ([en, ur]) => {
|
|
165
|
+
if (!active)
|
|
166
|
+
return;
|
|
48
167
|
checkers = { ltr: en ? await en : undefined, rtl: ur ? await ur : undefined };
|
|
49
|
-
|
|
168
|
+
syncBlocks();
|
|
169
|
+
for (const key of visible)
|
|
170
|
+
if (!cache.has(key))
|
|
171
|
+
schedule(key, 0);
|
|
50
172
|
});
|
|
51
173
|
return () => {
|
|
52
174
|
active = false;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
175
|
+
observer.disconnect();
|
|
176
|
+
unregisterUpdate();
|
|
177
|
+
unsubscribeWords();
|
|
178
|
+
for (const timer of timers.values())
|
|
179
|
+
clearTimeout(timer);
|
|
56
180
|
CSS.highlights.delete(HIGHLIGHT);
|
|
57
181
|
};
|
|
58
182
|
}, [editor]);
|
|
59
183
|
return null;
|
|
60
184
|
}
|
|
185
|
+
/** The text nodes of a rendered block, with where each one starts in the block's text. */
|
|
186
|
+
function textPieces(block) {
|
|
187
|
+
const pieces = [];
|
|
188
|
+
const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
189
|
+
let offset = 0;
|
|
190
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
191
|
+
const text = node;
|
|
192
|
+
pieces.push({ node: text, start: offset, end: offset + text.length });
|
|
193
|
+
offset += text.length;
|
|
194
|
+
}
|
|
195
|
+
return pieces;
|
|
196
|
+
}
|
|
@@ -12,6 +12,8 @@ export interface Speller {
|
|
|
12
12
|
suggest(word: string): string[];
|
|
13
13
|
}
|
|
14
14
|
type Loader = () => Promise<HunspellFiles>;
|
|
15
|
+
/** Sets where the English dictionary files are served from. Takes effect on the next load. */
|
|
16
|
+
export declare function setEnglishDictionaryBaseUrl(url: string): void;
|
|
15
17
|
/**
|
|
16
18
|
* Registers a dictionary for a language. Call once at startup with files you
|
|
17
19
|
* have licence to ship — e.g. the Urdu `.aff`/`.dic` pair — either as strings
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spellDictionaries.d.ts","sourceRoot":"","sources":["../../src/spellcheck/spellDictionaries.ts"],"names":[],"mappings":"AAGA,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,cAAc,CAAC;AAEzD,wFAAwF;AACxF,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACjC;AAED,KAAK,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"spellDictionaries.d.ts","sourceRoot":"","sources":["../../src/spellcheck/spellDictionaries.ts"],"names":[],"mappings":"AAGA,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,cAAc,CAAC;AAEzD,wFAAwF;AACxF,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACjC;AAED,KAAK,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;AAiB3C,8FAA8F;AAC9F,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAI7D;AAUD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAIpG;AAED,2DAA2D;AAC3D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAc3E;AAYD,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAOrD;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAGpF;AAED,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AASD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAMhD;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,EAAE,CAEzE"}
|
|
@@ -9,10 +9,20 @@ async function fetchText(url) {
|
|
|
9
9
|
throw new Error(`Spellcheck dictionary failed to load: ${url}`);
|
|
10
10
|
return response.text();
|
|
11
11
|
}
|
|
12
|
+
// Where the English dictionary files are. Built from this module's own URL, so the
|
|
13
|
+
// files shipped next to it are found; a host that bundles the editor can point this
|
|
14
|
+
// elsewhere with setEnglishDictionaryBaseUrl.
|
|
15
|
+
let englishBaseUrl = import.meta.url.replace(/[^/]*$/, 'dictionaries/en/');
|
|
16
|
+
/** Sets where the English dictionary files are served from. Takes effect on the next load. */
|
|
17
|
+
export function setEnglishDictionaryBaseUrl(url) {
|
|
18
|
+
englishBaseUrl = url.endsWith('/') ? url : `${url}/`;
|
|
19
|
+
loadedFiles.delete('en');
|
|
20
|
+
spellers.delete('en');
|
|
21
|
+
}
|
|
12
22
|
/** English ships with the editor: its files are fetched on first use. */
|
|
13
23
|
const englishLoader = async () => ({
|
|
14
|
-
aff: await fetchText(
|
|
15
|
-
dic: await fetchText(
|
|
24
|
+
aff: await fetchText(`${englishBaseUrl}index.aff`),
|
|
25
|
+
dic: await fetchText(`${englishBaseUrl}index.dic`),
|
|
16
26
|
});
|
|
17
27
|
loaders.set('en', englishLoader);
|
|
18
28
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ElementNode } from 'lexical';
|
|
1
2
|
import { type FindMatch } from '../find/findReplaceActions.js';
|
|
2
3
|
import { type Speller } from './spellDictionaries.js';
|
|
3
4
|
/** The checker for each text direction: English for LTR blocks, Urdu for RTL. */
|
|
@@ -18,4 +19,16 @@ export interface Misspelling {
|
|
|
18
19
|
}
|
|
19
20
|
/** Every word in the document its block's checker does not recognise. */
|
|
20
21
|
export declare function $collectMisspellings(checkers: Checkers): Misspelling[];
|
|
22
|
+
/** A misspelled word inside a block, as offsets into the block's text. */
|
|
23
|
+
export interface BlockMisspelling {
|
|
24
|
+
start: number;
|
|
25
|
+
end: number;
|
|
26
|
+
word: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The misspelled words inside one top-level block (a paragraph, a table, a list...),
|
|
30
|
+
* as offsets into the block's text. Words are found per parent (paragraph or cell),
|
|
31
|
+
* so text in different cells never forms one word. Must run inside an editor read.
|
|
32
|
+
*/
|
|
33
|
+
export declare function $misspellingsInBlock(block: ElementNode, checkers: Checkers): BlockMisspelling[];
|
|
21
34
|
//# 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,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
|
+
{"version":3,"file":"spellcheckActions.d.ts","sourceRoot":"","sources":["../../src/spellcheck/spellcheckActions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,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;AAeD,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,GAAG,gBAAgB,EAAE,CA8B/F"}
|
|
@@ -36,3 +36,38 @@ function segmentsOf(group, start, end) {
|
|
|
36
36
|
}
|
|
37
37
|
return segments;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The misspelled words inside one top-level block (a paragraph, a table, a list...),
|
|
41
|
+
* as offsets into the block's text. Words are found per parent (paragraph or cell),
|
|
42
|
+
* so text in different cells never forms one word. Must run inside an editor read.
|
|
43
|
+
*/
|
|
44
|
+
export function $misspellingsInBlock(block, checkers) {
|
|
45
|
+
const nodes = block.getAllTextNodes();
|
|
46
|
+
const starts = [];
|
|
47
|
+
let offset = 0;
|
|
48
|
+
for (const node of nodes) {
|
|
49
|
+
starts.push(offset);
|
|
50
|
+
offset += node.getTextContent().length;
|
|
51
|
+
}
|
|
52
|
+
const found = [];
|
|
53
|
+
let i = 0;
|
|
54
|
+
while (i < nodes.length) {
|
|
55
|
+
const parent = nodes[i].getParentOrThrow();
|
|
56
|
+
let j = i;
|
|
57
|
+
let text = '';
|
|
58
|
+
while (j < nodes.length && nodes[j].getParentOrThrow().getKey() === parent.getKey()) {
|
|
59
|
+
text += nodes[j].getTextContent();
|
|
60
|
+
j += 1;
|
|
61
|
+
}
|
|
62
|
+
const speller = parent.getDirection() === 'rtl' ? checkers.rtl : checkers.ltr;
|
|
63
|
+
if (speller) {
|
|
64
|
+
for (const span of wordsIn(text)) {
|
|
65
|
+
if (!speller.correct(span.word)) {
|
|
66
|
+
found.push({ start: starts[i] + span.start, end: starts[i] + span.end, word: span.word });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
i = j;
|
|
71
|
+
}
|
|
72
|
+
return found;
|
|
73
|
+
}
|
package/dist/theme/editor.css
CHANGED
|
@@ -19,19 +19,12 @@
|
|
|
19
19
|
its own height so only the canvas below scrolls. */
|
|
20
20
|
.likhari-toolbar {
|
|
21
21
|
display: flex;
|
|
22
|
-
/* Containing block for the hidden width-measuring clone (Toolbar.tsx):
|
|
23
|
-
without this, its position: absolute would resolve against the
|
|
24
|
-
viewport instead, and its intentionally-unwrapped (possibly very wide)
|
|
25
|
-
content could inflate the host page's scroll area instead of being
|
|
26
|
-
clipped by .likhari-root's overflow: hidden. */
|
|
27
|
-
position: relative;
|
|
28
22
|
flex-shrink: 0;
|
|
29
23
|
align-items: center;
|
|
30
24
|
gap: 8px;
|
|
31
25
|
min-height: 46px;
|
|
32
26
|
padding: 4px 8px;
|
|
33
27
|
background: var(--editor-paper-dim);
|
|
34
|
-
border-bottom: 1px solid var(--editor-line);
|
|
35
28
|
/* Wrap instead of scroll: an overflow container would clip menus and grow
|
|
36
29
|
a scrollbar. Dropdowns (Select/Menu) portal out, so nothing needs to
|
|
37
30
|
escape this box. */
|
|
@@ -57,55 +50,6 @@
|
|
|
57
50
|
pointer-events: auto;
|
|
58
51
|
}
|
|
59
52
|
|
|
60
|
-
.likhari-toolbar-group {
|
|
61
|
-
display: flex;
|
|
62
|
-
align-items: center;
|
|
63
|
-
gap: 2px;
|
|
64
|
-
padding: 0 6px;
|
|
65
|
-
border-inline-end: 0.5px solid var(--editor-line);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.likhari-toolbar-group:last-child {
|
|
69
|
-
border-inline-end: none;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.likhari-toolbar-button {
|
|
73
|
-
display: inline-flex;
|
|
74
|
-
align-items: center;
|
|
75
|
-
justify-content: center;
|
|
76
|
-
min-width: 30px;
|
|
77
|
-
height: 30px;
|
|
78
|
-
padding: 0 6px;
|
|
79
|
-
border-radius: 6px;
|
|
80
|
-
border: none;
|
|
81
|
-
background: transparent;
|
|
82
|
-
color: var(--editor-ink);
|
|
83
|
-
cursor: pointer;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.likhari-toolbar-button svg {
|
|
87
|
-
display: block;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.likhari-toolbar-button:hover {
|
|
91
|
-
background: var(--editor-line);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.likhari-toolbar-button[data-active='true'] {
|
|
95
|
-
background: var(--editor-accent-soft);
|
|
96
|
-
color: var(--editor-accent);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.likhari-toolbar-button:focus-visible {
|
|
100
|
-
outline: 2px solid var(--editor-accent);
|
|
101
|
-
outline-offset: 1px;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.likhari-toolbar-button:disabled {
|
|
105
|
-
opacity: 0.4;
|
|
106
|
-
cursor: not-allowed;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
53
|
/* Mantine Select inside the toolbar — sized to the 30px control height and
|
|
110
54
|
themed from the --editor-* tokens (the closed input only; the dropdown is
|
|
111
55
|
portaled to <body>, outside the token scope, so it uses Mantine's own
|
|
@@ -125,35 +69,44 @@
|
|
|
125
69
|
cursor: not-allowed;
|
|
126
70
|
}
|
|
127
71
|
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
72
|
+
/* A cluster of related buttons (undo/redo, bold/italic/underline/strikethrough, ...) reads as one control. */
|
|
73
|
+
.likhari-toolbar--plain .likhari-toolbar-cluster {
|
|
74
|
+
border: none;
|
|
75
|
+
background: transparent;
|
|
131
76
|
}
|
|
132
77
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
78
|
+
.likhari-toolbar--plain .likhari-toolbar-cluster [data-toolbar-toggle] + [data-toolbar-toggle] {
|
|
79
|
+
border-inline-start: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.likhari-toolbar-cluster {
|
|
83
|
+
border: 1px solid var(--editor-line-strong);
|
|
84
|
+
border-radius: 6px;
|
|
85
|
+
background: var(--editor-paper);
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.likhari-toolbar-cluster [data-toolbar-toggle] {
|
|
90
|
+
border: none;
|
|
91
|
+
border-radius: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.likhari-toolbar-cluster [data-toolbar-toggle] + [data-toolbar-toggle] {
|
|
95
|
+
border-inline-start: 1px solid var(--editor-line);
|
|
142
96
|
}
|
|
143
97
|
|
|
144
|
-
/* Save
|
|
145
|
-
|
|
146
|
-
.likhari-toolbar-button[data-dirty='true'] {
|
|
98
|
+
/* Save: filled accent while there are unsaved changes, back to the plain outline once saved. */
|
|
99
|
+
.likhari-toolbar [data-dirty='true'] {
|
|
147
100
|
background: var(--editor-accent);
|
|
148
101
|
color: var(--editor-paper);
|
|
149
102
|
}
|
|
150
103
|
|
|
151
|
-
.likhari-toolbar
|
|
104
|
+
.likhari-toolbar [data-dirty='true']:hover {
|
|
152
105
|
background: var(--editor-accent);
|
|
153
106
|
filter: brightness(1.1);
|
|
154
107
|
}
|
|
155
108
|
|
|
156
|
-
.likhari-toolbar
|
|
109
|
+
.likhari-toolbar [data-dirty='false'] {
|
|
157
110
|
color: var(--editor-accent);
|
|
158
111
|
}
|
|
159
112
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inshapardaz/likhari-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"description": "React implementation of the Likhari editor, built on @lexical/react.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
"nspell": "^2.1.5"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@mantine/core": "^9.
|
|
51
|
-
"@mantine/hooks": "^9.
|
|
50
|
+
"@mantine/core": "^9.7.0",
|
|
51
|
+
"@mantine/hooks": "^9.7.0",
|
|
52
52
|
"react": "^19.2.0",
|
|
53
53
|
"react-dom": "^19.2.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@mantine/core": "^9.
|
|
57
|
-
"@mantine/hooks": "^9.
|
|
56
|
+
"@mantine/core": "^9.7.0",
|
|
57
|
+
"@mantine/hooks": "^9.7.0",
|
|
58
58
|
"@types/nspell": "^2.1.6"
|
|
59
59
|
}
|
|
60
60
|
}
|