diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 653e7dd0b2b..b340167c116 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2084,6 +2084,8 @@ }, "editorfeatures": { "title": "Features", + "mathlive_enabled": "MathLive visual editor", + "mathlive_description": "Math equations can be edited using a visual editor.", "emoji_completion_enabled": "Emoji auto-completion", "emoji_completion_description": "Emojis can be easily inserted into text by typing `:`, followed by the name of an emoji.", "note_completion_enabled": "Note auto-completion", diff --git a/apps/client/src/widgets/type_widgets/options/text_notes.tsx b/apps/client/src/widgets/type_widgets/options/text_notes.tsx index 009def29a93..81f194dfab7 100644 --- a/apps/client/src/widgets/type_widgets/options/text_notes.tsx +++ b/apps/client/src/widgets/type_widgets/options/text_notes.tsx @@ -116,12 +116,21 @@ function ToolbarIcon({ wide }: { wide?: boolean }) { } function EditorFeatures() { + const [mathFieldEnabled, setMathFieldEnabled] = useTriliumOptionBool("mathFieldEnabled"); const [emojiCompletionEnabled, setEmojiCompletionEnabled] = useTriliumOptionBool("textNoteEmojiCompletionEnabled"); const [noteCompletionEnabled, setNoteCompletionEnabled] = useTriliumOptionBool("textNoteCompletionEnabled"); const [slashCommandsEnabled, setSlashCommandsEnabled] = useTriliumOptionBool("textNoteSlashCommandsEnabled"); return ( + + ([ "textNoteEmojiCompletionEnabled", "textNoteCompletionEnabled", "textNoteSlashCommandsEnabled", + "mathFieldEnabled", "includeNoteDefaultBoxSize", "layoutOrientation", "backgroundEffects", diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index 4bff15d91e3..3c1bc01d9cb 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -223,6 +223,7 @@ const defaultOptions: DefaultOption[] = [ { name: "textNoteEmojiCompletionEnabled", value: "true", isSynced: true }, { name: "textNoteCompletionEnabled", value: "true", isSynced: true }, { name: "textNoteSlashCommandsEnabled", value: "true", isSynced: true }, + { name: "mathFieldEnabled", value: "true", isSynced: true }, { name: "includeNoteDefaultBoxSize", value: "medium", isSynced: true }, // HTML import configuration diff --git a/package.json b/package.json index 9044cd6abad..940ed69603b 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,8 @@ "pnpm": { "patchedDependencies": { "@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch", - "@ckeditor/ckeditor5-code-block": "patches/@ckeditor__ckeditor5-code-block.patch" + "@ckeditor/ckeditor5-code-block": "patches/@ckeditor__ckeditor5-code-block.patch", + "mathlive@0.109.1": "patches/mathlive@0.109.1.patch" }, "overrides": { "@codemirror/language": "6.12.3", diff --git a/packages/ckeditor5-math/src/augmentation.ts b/packages/ckeditor5-math/src/augmentation.ts index 855e42133f3..10c0e4a0020 100644 --- a/packages/ckeditor5-math/src/augmentation.ts +++ b/packages/ckeditor5-math/src/augmentation.ts @@ -27,6 +27,7 @@ declare module 'ckeditor5' { className?: string | undefined; forceOutputType?: boolean | undefined; enablePreview?: boolean | undefined; + enableMathField?: boolean | undefined; previewClassName?: Array | undefined; popupClassName?: Array | undefined; katexRenderOptions?: Partial | undefined; diff --git a/packages/ckeditor5-math/src/mathediting.ts b/packages/ckeditor5-math/src/mathediting.ts index 53b50614bbb..9bebb8dcc85 100644 --- a/packages/ckeditor5-math/src/mathediting.ts +++ b/packages/ckeditor5-math/src/mathediting.ts @@ -19,6 +19,7 @@ export default class MathEditing extends Plugin { className: 'math-tex', forceOutputType: false, enablePreview: true, + enableMathField: true, previewClassName: [], popupClassName: [], katexRenderOptions: {} diff --git a/packages/ckeditor5-math/src/mathui.ts b/packages/ckeditor5-math/src/mathui.ts index 3e3da2744dd..79e9d5ec03b 100644 --- a/packages/ckeditor5-math/src/mathui.ts +++ b/packages/ckeditor5-math/src/mathui.ts @@ -83,7 +83,8 @@ export default class MathUI extends Plugin { katexRenderOptions: mathConfig.katexRenderOptions! }, mathConfig.enablePreview, - mathConfig.popupClassName! + mathConfig.popupClassName!, + mathConfig.enableMathField ); formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' ); diff --git a/packages/ckeditor5-math/src/ui/mainformview.ts b/packages/ckeditor5-math/src/ui/mainformview.ts index c5989cfbd40..c4e5b822b13 100644 --- a/packages/ckeditor5-math/src/ui/mainformview.ts +++ b/packages/ckeditor5-math/src/ui/mainformview.ts @@ -22,13 +22,14 @@ export default class MainFormView extends View { locale: Locale, mathViewOptions: MathViewOptions, previewEnabled = false, - popupClassName: Array = [] + popupClassName: Array = [], + enableMathField = true ) { super( locale ); const t = locale.t; // Create views - this.mathInputView = new MathInputView( locale ); + this.mathInputView = new MathInputView( locale, enableMathField ); this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save', 'submit' ); this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel' ); this.cancelButtonView.delegate( 'execute' ).to( this, 'cancel' ); diff --git a/packages/ckeditor5-math/src/ui/mathinputview.ts b/packages/ckeditor5-math/src/ui/mathinputview.ts index 2eb0347e58e..be35bb6710c 100644 --- a/packages/ckeditor5-math/src/ui/mathinputview.ts +++ b/packages/ckeditor5-math/src/ui/mathinputview.ts @@ -4,18 +4,6 @@ import { View, type Locale, type FocusableView } from 'ckeditor5'; import 'mathlive/fonts.css'; // Auto-bundles offline fonts import 'mathlive/static.css'; // Static styles for mathlive -declare global { - interface Window { - mathVirtualKeyboard?: { - visible: boolean; - show: () => void; - hide: () => void; - addEventListener: ( event: string, cb: () => void ) => void; - removeEventListener: ( event: string, cb: () => void ) => void; - }; - } -} - interface MathFieldElement extends HTMLElement { value: string; readOnly: boolean; @@ -64,21 +52,28 @@ export default class MathInputView extends View { private _destroyed = false; private _vkGeometryHandler?: () => void; private _updating = false; + private _enableMathField: boolean; private static _configured = false; - constructor( locale: Locale ) { + constructor( locale: Locale, enableMathField = true ) { super( locale ); + this._enableMathField = enableMathField; this.latexTextAreaView = new LatexTextAreaView( locale ); this.mathFieldFocusableView = new MathFieldFocusableView( locale, this ); this.set( 'value', null ); this.set( 'isReadOnly', false ); + const children: Array = []; + // Only include the MathLive container in the DOM when the feature is enabled + if ( this._enableMathField ) { + children.push( { tag: 'div', attributes: { class: [ 'ck-mathlive-container' ] } } ); + } + children.push( + { tag: 'label', attributes: { class: [ 'ck-latex-label' ] }, children: [ locale.t( 'LaTeX' ) ] }, + { tag: 'div', attributes: { class: [ 'ck-latex-wrapper' ] }, children: [ this.latexTextAreaView ] } + ); this.setTemplate( { tag: 'div', attributes: { class: [ 'ck', 'ck-math-input' ] }, - children: [ - { tag: 'div', attributes: { class: [ 'ck-mathlive-container' ] } }, - { tag: 'label', attributes: { class: [ 'ck-latex-label' ] }, children: [ locale.t( 'LaTeX' ) ] }, - { tag: 'div', attributes: { class: [ 'ck-latex-wrapper' ] }, children: [ this.latexTextAreaView ] } - ] + children } ); } @@ -135,7 +130,7 @@ export default class MathInputView extends View { } ); // Handle virtual keyboard geometry changes - const vk = window.mathVirtualKeyboard; + const vk = (window as any).mathVirtualKeyboard; if ( vk && !this._vkGeometryHandler ) { this._vkGeometryHandler = () => { if ( vk.visible && this.mathfield ) { @@ -149,7 +144,9 @@ export default class MathInputView extends View { if ( textarea.value !== initial ) { textarea.value = initial; } - this._loadMathLive(); + if ( this._enableMathField ) { + this._loadMathLive(); + } } // Loads the MathLive library dynamically @@ -198,7 +195,10 @@ export default class MathInputView extends View { // Set shortcuts after mounting (accessing inlineShortcuts requires mounted element) try { if ( mf.inlineShortcuts ) { - mf.inlineShortcuts = { ...mf.inlineShortcuts, dx: 'dx', dy: 'dy', dt: 'dt' }; + // Allows external listeners to inject custom MathLive shortcuts by mutating event.detail. + const customShortcuts: Record = {}; + document.dispatchEvent( new CustomEvent( 'mathlive:custom-shortcuts', { detail: customShortcuts } ) ); + mf.inlineShortcuts = { ...mf.inlineShortcuts, dx: 'dx', dy: 'dy', dt: 'dt', ...customShortcuts }; } } catch { // Inline shortcut configuration is optional; ignore failures to avoid breaking the math field. @@ -247,16 +247,20 @@ export default class MathInputView extends View { } public hideKeyboard(): void { - window.mathVirtualKeyboard?.hide(); + (window as any).mathVirtualKeyboard?.hide(); } public focus(): void { - this.mathfield?.focus(); + if ( this.mathfield ) { + this.mathfield.focus(); + } else { + this.latexTextAreaView.focus(); + } } public override destroy(): void { this._destroyed = true; - const vk = window.mathVirtualKeyboard; + const vk = (window as any).mathVirtualKeyboard; if ( vk && this._vkGeometryHandler ) { vk.removeEventListener( 'geometrychange', this._vkGeometryHandler ); this._vkGeometryHandler = undefined; diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index 9f7db1d04b7..db31a6e7d18 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -167,6 +167,8 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions + if (offset < first || offset > last) return false; + if (!atom.hasChildren) return true; + const firstChild = includeFirstAtoms ? atom.firstChild : firstNonFirstChild(atom); +- if (!firstChild) return false; ++ if (!firstChild) return true; + const lastChild = includeFirstAtoms ? atom.lastChild : lastNonFirstChild(atom); +- if (!lastChild) return false; ++ if (!lastChild) return true; + const firstOffset = model.offsetOf(firstChild); + if (firstOffset >= first && firstOffset <= last) { + const lastOffset = model.offsetOf(lastChild); +diff --git a/mathlive.mjs b/mathlive.mjs +index 16bde454e19b5e83bbc60cd98f0bd1ea5e2359b1..72a64a4f4feb646d89d9b9b2ec331f608a012d20 100644 +--- a/mathlive.mjs ++++ b/mathlive.mjs +@@ -38996,9 +38996,9 @@ function atomIsInRange(model, atom, first, last, includeFirstAtoms) { + if (offset < first || offset > last) return false; + if (!atom.hasChildren) return true; + const firstChild = includeFirstAtoms ? atom.firstChild : firstNonFirstChild(atom); +- if (!firstChild) return false; ++ if (!firstChild) return true; + const lastChild = includeFirstAtoms ? atom.lastChild : lastNonFirstChild(atom); +- if (!lastChild) return false; ++ if (!lastChild) return true; + const firstOffset = model.offsetOf(firstChild); + if (firstOffset >= first && firstOffset <= last) { + const lastOffset = model.offsetOf(lastChild); +diff --git a/package.json b/package.json +index 343841095cbfcb883d0530ae77809db996a206db..a9aee5cf1f66a8504d82b4c66f412742b284b814 100644 +--- a/package.json ++++ b/package.json +@@ -29,7 +29,7 @@ + "mathquill" + ], + "main": "./mathlive.min.js", +- "module": "./mathlive.min.mjs", ++ "module": "./mathlive.mjs", + "types": "./types/mathlive.d.ts", + "exports": { + "./vue": "./vue-mathlive.mjs", +@@ -39,8 +39,8 @@ + "browser": { + "production": { + "types": "./types/mathlive.d.ts", +- "import": "./mathlive.min.mjs", +- "require": "./mathlive.min.js" ++ "import": "./mathlive.mjs", ++ "require": "./mathlive.js" + }, + "development": { + "types": "./types/mathlive.d.ts", +@@ -54,8 +54,8 @@ + }, + "default": { + "types": "./types/mathlive.d.ts", +- "import": "./mathlive.min.mjs", +- "require": "./mathlive.min.js" ++ "import": "./mathlive.mjs", ++ "require": "./mathlive.js" + } + }, + "./ssr": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00df64709bf..f904581b79f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,6 +79,9 @@ patchedDependencies: '@ckeditor/ckeditor5-mention': hash: 70377e48a04e2f45abba79aa35f3d5527ec7aa7ffce44056f9957a49a24a4a60 path: patches/@ckeditor__ckeditor5-mention.patch + mathlive@0.109.1: + hash: 6b87b7adb4b7e0597fd85aa07415768aa157f291b9dd075f1d73f1017f125c9f + path: patches/mathlive@0.109.1.patch importers: @@ -1104,7 +1107,7 @@ importers: version: 48.0.0 mathlive: specifier: 0.109.1 - version: 0.109.1 + version: 0.109.1(patch_hash=6b87b7adb4b7e0597fd85aa07415768aa157f291b9dd075f1d73f1017f125c9f) devDependencies: '@ckeditor/ckeditor5-inspector': specifier: '>=4.1.0' @@ -25795,7 +25798,7 @@ snapshots: math-intrinsics@1.1.0: {} - mathlive@0.109.1: + mathlive@0.109.1(patch_hash=6b87b7adb4b7e0597fd85aa07415768aa157f291b9dd075f1d73f1017f125c9f): dependencies: '@cortex-js/compute-engine': 0.30.2