mirror of
https://github.com/zadam/trilium.git
synced 2026-01-17 12:04:24 +01:00
MathLive virtual keyboard only appears when focusing the mathfield
This commit is contained in:
parent
f1b2d0b870
commit
70f46de2d8
@ -1,198 +1,208 @@
|
||||
import { View, type Locale } from 'ckeditor5';
|
||||
|
||||
interface MathFieldElement extends HTMLElement {
|
||||
value: string;
|
||||
readOnly: boolean;
|
||||
mathVirtualKeyboardPolicy: string;
|
||||
inlineShortcuts?: Record<string, string>;
|
||||
setValue(value: string, options?: { silenceNotifications?: boolean }): void;
|
||||
value: string;
|
||||
readOnly: boolean;
|
||||
mathVirtualKeyboardPolicy: string;
|
||||
inlineShortcuts?: Record<string, string>;
|
||||
setValue( value: string, options?: { silenceNotifications?: boolean } ): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combined math input with MathLive visual editor and raw LaTeX textarea.
|
||||
*/
|
||||
export default class MathInputView extends View {
|
||||
public declare value: string | null;
|
||||
public declare isReadOnly: boolean;
|
||||
public declare value: string | null;
|
||||
public declare isReadOnly: boolean;
|
||||
|
||||
public mathfield: MathFieldElement | null = null;
|
||||
private _textarea: HTMLTextAreaElement | null = null;
|
||||
public mathfield: MathFieldElement | null = null;
|
||||
private _textarea: HTMLTextAreaElement | null = null;
|
||||
|
||||
constructor(locale: Locale) {
|
||||
super(locale);
|
||||
const t = locale.t;
|
||||
constructor( locale: Locale ) {
|
||||
super( locale );
|
||||
const t = locale.t;
|
||||
|
||||
this.set('value', null);
|
||||
this.set('isReadOnly', false);
|
||||
this.set( 'value', null );
|
||||
this.set( 'isReadOnly', false );
|
||||
|
||||
this.setTemplate({
|
||||
tag: 'div',
|
||||
attributes: {
|
||||
class: ['ck', 'ck-math-input']
|
||||
},
|
||||
children: [
|
||||
// MathLive container
|
||||
{
|
||||
tag: 'div',
|
||||
attributes: { class: ['ck-mathlive-container'] }
|
||||
},
|
||||
// LaTeX label
|
||||
{
|
||||
tag: 'label',
|
||||
attributes: { class: ['ck-latex-label'] },
|
||||
children: [t('LaTeX')]
|
||||
},
|
||||
// Raw LaTeX wrapper
|
||||
{
|
||||
tag: 'div',
|
||||
attributes: { class: ['ck-latex-wrapper'] },
|
||||
children: [
|
||||
{
|
||||
tag: 'textarea',
|
||||
attributes: {
|
||||
class: ['ck', 'ck-textarea', 'ck-latex-textarea'],
|
||||
autocapitalize: 'off',
|
||||
autocomplete: 'off',
|
||||
autocorrect: 'off',
|
||||
spellcheck: 'false'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
this.setTemplate( {
|
||||
tag: 'div',
|
||||
attributes: {
|
||||
class: [ 'ck', 'ck-math-input' ]
|
||||
},
|
||||
children: [
|
||||
// MathLive container
|
||||
{
|
||||
tag: 'div',
|
||||
attributes: { class: [ 'ck-mathlive-container' ] }
|
||||
},
|
||||
// LaTeX label
|
||||
{
|
||||
tag: 'label',
|
||||
attributes: { class: [ 'ck-latex-label' ] },
|
||||
children: [ t( 'LaTeX' ) ]
|
||||
},
|
||||
// Raw LaTeX wrapper
|
||||
{
|
||||
tag: 'div',
|
||||
attributes: { class: [ 'ck-latex-wrapper' ] },
|
||||
children: [
|
||||
{
|
||||
tag: 'textarea',
|
||||
attributes: {
|
||||
class: [ 'ck', 'ck-textarea', 'ck-latex-textarea' ],
|
||||
autocapitalize: 'off',
|
||||
autocomplete: 'off',
|
||||
autocorrect: 'off',
|
||||
spellcheck: 'false'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
} );
|
||||
}
|
||||
|
||||
public override render(): void {
|
||||
super.render();
|
||||
public override render(): void {
|
||||
super.render();
|
||||
|
||||
this._textarea = this.element!.querySelector('.ck-latex-textarea') as HTMLTextAreaElement;
|
||||
this._textarea.value = this.value ?? '';
|
||||
this._textarea.readOnly = this.isReadOnly;
|
||||
this._textarea = this.element!.querySelector( '.ck-latex-textarea' ) as HTMLTextAreaElement;
|
||||
this._textarea.value = this.value ?? '';
|
||||
this._textarea.readOnly = this.isReadOnly;
|
||||
|
||||
this._loadMathLive();
|
||||
this._loadMathLive();
|
||||
|
||||
// Textarea -> observable (and sync to mathfield)
|
||||
this._textarea.addEventListener('input', () => {
|
||||
const val = this._textarea!.value;
|
||||
if (this.mathfield) {
|
||||
this.mathfield.setValue(val, { silenceNotifications: true });
|
||||
}
|
||||
this.value = val.length ? val : null;
|
||||
});
|
||||
// Textarea -> observable (and sync to mathfield)
|
||||
this._textarea.addEventListener( 'input', () => {
|
||||
const val = this._textarea!.value;
|
||||
if ( this.mathfield ) {
|
||||
this.mathfield.setValue( val, { silenceNotifications: true } );
|
||||
}
|
||||
this.value = val.length ? val : null;
|
||||
} );
|
||||
|
||||
// Observable -> textarea and mathfield
|
||||
this.on('change:value', (_evt, _name, newValue) => {
|
||||
const val = newValue ?? '';
|
||||
if (this._textarea && this._textarea.value !== val) {
|
||||
this._textarea.value = val;
|
||||
}
|
||||
if (this.mathfield && this.mathfield.value !== val) {
|
||||
this.mathfield.setValue(val, { silenceNotifications: true });
|
||||
}
|
||||
});
|
||||
// Observable -> textarea and mathfield
|
||||
this.on( 'change:value', ( _evt, _name, newValue ) => {
|
||||
const val = newValue ?? '';
|
||||
if ( this._textarea && this._textarea.value !== val ) {
|
||||
this._textarea.value = val;
|
||||
}
|
||||
if ( this.mathfield && this.mathfield.value !== val ) {
|
||||
this.mathfield.setValue( val, { silenceNotifications: true } );
|
||||
}
|
||||
} );
|
||||
|
||||
this.on('change:isReadOnly', (_evt, _name, newValue) => {
|
||||
if (this._textarea) this._textarea.readOnly = newValue;
|
||||
if (this.mathfield) this.mathfield.readOnly = newValue;
|
||||
});
|
||||
}
|
||||
this.on( 'change:isReadOnly', ( _evt, _name, newValue ) => {
|
||||
if ( this._textarea ) { this._textarea.readOnly = newValue; }
|
||||
if ( this.mathfield ) { this.mathfield.readOnly = newValue; }
|
||||
} );
|
||||
}
|
||||
|
||||
private async _loadMathLive(): Promise<void> {
|
||||
try {
|
||||
await import('mathlive');
|
||||
await customElements.whenDefined('math-field');
|
||||
private async _loadMathLive(): Promise<void> {
|
||||
try {
|
||||
await import( 'mathlive' );
|
||||
await customElements.whenDefined( 'math-field' );
|
||||
|
||||
// Disable MathLive sounds
|
||||
const MathfieldClass = customElements.get('math-field') as any;
|
||||
if (MathfieldClass) {
|
||||
MathfieldClass.soundsDirectory = null;
|
||||
MathfieldClass.plonkSound = null;
|
||||
}
|
||||
// Disable MathLive sounds
|
||||
const MathfieldClass = customElements.get( 'math-field' ) as any;
|
||||
if ( MathfieldClass ) {
|
||||
MathfieldClass.soundsDirectory = null;
|
||||
MathfieldClass.plonkSound = null;
|
||||
}
|
||||
|
||||
if (!this.element) return;
|
||||
this._createMathField();
|
||||
} catch (error) {
|
||||
console.error('MathLive load failed:', error);
|
||||
const container = this.element?.querySelector('.ck-mathlive-container');
|
||||
if (container) {
|
||||
container.textContent = 'Math editor unavailable';
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !this.element ) { return; }
|
||||
this._createMathField();
|
||||
} catch ( error ) {
|
||||
console.error( 'MathLive load failed:', error );
|
||||
const container = this.element?.querySelector( '.ck-mathlive-container' );
|
||||
if ( container ) {
|
||||
container.textContent = 'Math editor unavailable';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _createMathField(): void {
|
||||
const container = this.element?.querySelector('.ck-mathlive-container');
|
||||
if (!container) return;
|
||||
private _createMathField(): void {
|
||||
const container = this.element?.querySelector( '.ck-mathlive-container' );
|
||||
if ( !container ) { return; }
|
||||
|
||||
const mathfield = document.createElement('math-field') as MathFieldElement;
|
||||
mathfield.mathVirtualKeyboardPolicy = 'manual';
|
||||
const mathfield = document.createElement( 'math-field' ) as MathFieldElement;
|
||||
mathfield.mathVirtualKeyboardPolicy = 'auto';
|
||||
|
||||
// Add common shortcuts
|
||||
mathfield.addEventListener('mount', () => {
|
||||
mathfield.inlineShortcuts = {
|
||||
...mathfield.inlineShortcuts,
|
||||
dx: 'dx',
|
||||
dy: 'dy',
|
||||
dt: 'dt'
|
||||
};
|
||||
}, { once: true });
|
||||
// Add common shortcuts
|
||||
mathfield.addEventListener( 'mount', () => {
|
||||
mathfield.inlineShortcuts = {
|
||||
...mathfield.inlineShortcuts,
|
||||
dx: 'dx',
|
||||
dy: 'dy',
|
||||
dt: 'dt'
|
||||
};
|
||||
}, { once: true } );
|
||||
|
||||
// Set initial value (may have been set before MathLive loaded)
|
||||
try {
|
||||
mathfield.value = this.value ?? '';
|
||||
} catch { /* MathLive may not be ready */ }
|
||||
mathfield.readOnly = this.isReadOnly;
|
||||
// Focus mathfield when virtual keyboard button is clicked
|
||||
mathfield.addEventListener( 'mount', () => {
|
||||
const toggleBtn = mathfield.shadowRoot?.querySelector( '[part="virtual-keyboard-toggle"]' ) as HTMLButtonElement;
|
||||
if ( toggleBtn ) {
|
||||
toggleBtn.addEventListener( 'click', () => {
|
||||
mathfield.focus();
|
||||
} );
|
||||
}
|
||||
}, { once: true } );
|
||||
|
||||
if (this._textarea && this.value) {
|
||||
this._textarea.value = this.value;
|
||||
}
|
||||
// Set initial value (may have been set before MathLive loaded)
|
||||
try {
|
||||
mathfield.value = this.value ?? '';
|
||||
} catch { /* MathLive may not be ready */ }
|
||||
mathfield.readOnly = this.isReadOnly;
|
||||
|
||||
// MathLive -> textarea and observable
|
||||
mathfield.addEventListener('input', () => {
|
||||
try {
|
||||
const val = mathfield.value;
|
||||
if (this._textarea) this._textarea.value = val;
|
||||
this.value = val.length ? val : null;
|
||||
} catch { /* MathLive may not be ready */ }
|
||||
});
|
||||
if ( this._textarea && this.value ) {
|
||||
this._textarea.value = this.value;
|
||||
}
|
||||
|
||||
// Observable -> MathLive
|
||||
this.on('change:value', (_evt, _name, newValue) => {
|
||||
try {
|
||||
const val = newValue ?? '';
|
||||
if (mathfield.value !== val) {
|
||||
mathfield.setValue(val, { silenceNotifications: true });
|
||||
}
|
||||
} catch { /* MathLive may not be ready */ }
|
||||
});
|
||||
// MathLive -> textarea and observable
|
||||
mathfield.addEventListener( 'input', () => {
|
||||
try {
|
||||
const val = mathfield.value;
|
||||
if ( this._textarea ) { this._textarea.value = val; }
|
||||
this.value = val.length ? val : null;
|
||||
} catch { /* MathLive may not be ready */ }
|
||||
} );
|
||||
|
||||
container.appendChild(mathfield);
|
||||
this.mathfield = mathfield;
|
||||
}
|
||||
// Observable -> MathLive
|
||||
this.on( 'change:value', ( _evt, _name, newValue ) => {
|
||||
try {
|
||||
const val = newValue ?? '';
|
||||
if ( mathfield.value !== val ) {
|
||||
mathfield.setValue( val, { silenceNotifications: true } );
|
||||
}
|
||||
} catch { /* MathLive may not be ready */ }
|
||||
} );
|
||||
|
||||
public focus(): void {
|
||||
this.mathfield?.focus();
|
||||
}
|
||||
container.appendChild( mathfield );
|
||||
this.mathfield = mathfield;
|
||||
}
|
||||
|
||||
public hideKeyboard(): void {
|
||||
if (typeof window !== 'undefined' && window.mathVirtualKeyboard?.visible) {
|
||||
window.mathVirtualKeyboard.hide();
|
||||
}
|
||||
}
|
||||
public focus(): void {
|
||||
this.mathfield?.focus();
|
||||
}
|
||||
|
||||
public override destroy(): void {
|
||||
// Hide keyboard before destroying
|
||||
this.hideKeyboard();
|
||||
public hideKeyboard(): void {
|
||||
if ( typeof window !== 'undefined' && window.mathVirtualKeyboard?.visible ) {
|
||||
window.mathVirtualKeyboard.hide();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.mathfield) {
|
||||
try {
|
||||
this.mathfield.blur();
|
||||
this.mathfield.remove();
|
||||
} catch { /* MathLive cleanup error */ }
|
||||
this.mathfield = null;
|
||||
}
|
||||
this._textarea = null;
|
||||
super.destroy();
|
||||
}
|
||||
public override destroy(): void {
|
||||
// Hide keyboard before destroying
|
||||
this.hideKeyboard();
|
||||
|
||||
if ( this.mathfield ) {
|
||||
try {
|
||||
this.mathfield.blur();
|
||||
this.mathfield.remove();
|
||||
} catch { /* MathLive cleanup error */ }
|
||||
this.mathfield = null;
|
||||
}
|
||||
this._textarea = null;
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user