diff --git a/src/autoformatmath.js b/src/autoformatmath.js deleted file mode 100644 index 18b55015a..000000000 --- a/src/autoformatmath.js +++ /dev/null @@ -1,49 +0,0 @@ -import { Plugin } from 'ckeditor5/src/core'; -import { global, logWarning } from 'ckeditor5/src/utils'; -import blockAutoformatEditing from '@ckeditor/ckeditor5-autoformat/src/blockautoformatediting'; -import Math from './math'; - -export default class AutoformatMath extends Plugin { - static get requires() { - return [ Math, 'Autoformat' ]; - } - - /** - * @inheritDoc - */ - init() { - const editor = this.editor; - - if ( !editor.plugins.has( 'Math' ) ) { - logWarning( 'autoformat-math-feature-missing', editor ); - } - } - - afterInit() { - const editor = this.editor; - const command = editor.commands.get( 'math' ); - - if ( command ) { - const callback = () => { - if ( !command.isEnabled ) { - return false; - } - - command.display = true; - - // Wait until selection is removed. - global.window.setTimeout( - () => editor.plugins.get( 'MathUI' )._showUI(), - 50 - ); - }; - - blockAutoformatEditing( editor, this, /^\$\$$/, callback ); - blockAutoformatEditing( editor, this, /^\\\[$/, callback ); - } - } - - static get pluginName() { - return 'AutoformatMath'; - } -} diff --git a/src/automath.js b/src/automath.js deleted file mode 100644 index a3b06ddc5..000000000 --- a/src/automath.js +++ /dev/null @@ -1,120 +0,0 @@ -import { Clipboard } from 'ckeditor5/src/clipboard'; -import { Plugin } from 'ckeditor5/src/core'; -import { LivePosition, LiveRange } from 'ckeditor5/src/engine'; -import { Undo } from 'ckeditor5/src/undo'; -import { global } from 'ckeditor5/src/utils'; -import { extractDelimiters, hasDelimiters, delimitersCounts } from './utils'; - -export default class AutoMath extends Plugin { - static get requires() { - return [ Clipboard, Undo ]; - } - - static get pluginName() { - return 'AutoMath'; - } - - constructor( editor ) { - super( editor ); - - this._timeoutId = null; - - this._positionToInsert = null; - } - - init() { - const editor = this.editor; - const modelDocument = editor.model.document; - - this.listenTo( editor.plugins.get( Clipboard ), 'inputTransformation', () => { - const firstRange = modelDocument.selection.getFirstRange(); - - const leftLivePosition = LivePosition.fromPosition( firstRange.start ); - leftLivePosition.stickiness = 'toPrevious'; - - const rightLivePosition = LivePosition.fromPosition( firstRange.end ); - rightLivePosition.stickiness = 'toNext'; - - modelDocument.once( 'change:data', () => { - this._mathBetweenPositions( leftLivePosition, rightLivePosition ); - - leftLivePosition.detach(); - rightLivePosition.detach(); - }, { priority: 'high' } ); - } ); - - editor.commands.get( 'undo' ).on( 'execute', () => { - if ( this._timeoutId ) { - global.window.clearTimeout( this._timeoutId ); - this._positionToInsert.detach(); - - this._timeoutId = null; - this._positionToInsert = null; - } - }, { priority: 'high' } ); - } - - _mathBetweenPositions( leftPosition, rightPosition ) { - const editor = this.editor; - - const mathConfig = this.editor.config.get( 'math' ); - - const equationRange = new LiveRange( leftPosition, rightPosition ); - const walker = equationRange.getWalker( { ignoreElementEnd: true } ); - - let text = ''; - - // Get equation text - for ( const node of walker ) { - if ( node.item.is( '$textProxy' ) ) { - text += node.item.data; - } - } - - text = text.trim(); - - // Skip if don't have delimiters - if ( !hasDelimiters( text ) || delimitersCounts( text ) !== 2 ) { - return; - } - - const mathCommand = editor.commands.get( 'math' ); - - // Do not anything if math element cannot be inserted at the current position - if ( !mathCommand.isEnabled ) { - return; - } - - this._positionToInsert = LivePosition.fromPosition( leftPosition ); - - // With timeout user can undo conversation if want use plain text - this._timeoutId = global.window.setTimeout( () => { - editor.model.change( writer => { - this._timeoutId = null; - - writer.remove( equationRange ); - - let insertPosition; - - // Check if position where the math element should be inserted is still valid. - if ( this._positionToInsert.root.rootName !== '$graveyard' ) { - insertPosition = this._positionToInsert; - } - - editor.model.change( innerWriter => { - const params = Object.assign( extractDelimiters( text ), { - type: mathConfig.outputType - } ); - const mathElement = innerWriter.createElement( params.display ? 'mathtex-display' : 'mathtex-inline', params ); - - editor.model.insertContent( mathElement, insertPosition ); - - innerWriter.setSelection( mathElement, 'on' ); - } ); - - this._positionToInsert.detach(); - this._positionToInsert = null; - } ); - }, 100 ); - } -} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 433ed384f..000000000 --- a/src/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @module math - */ - -export { default as Math } from './math'; -export { default as AutoformatMath } from './autoformatmath'; diff --git a/src/math.js b/src/math.js deleted file mode 100644 index 3a3b05ee1..000000000 --- a/src/math.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Plugin } from 'ckeditor5/src/core'; -import { Widget } from 'ckeditor5/src/widget'; - -import MathUI from './mathui'; -import MathEditing from './mathediting'; -import AutoMath from './automath'; - -export default class Math extends Plugin { - static get requires() { - return [ MathEditing, MathUI, AutoMath, Widget ]; - } - - static get pluginName() { - return 'Math'; - } -} diff --git a/src/mathcommand.js b/src/mathcommand.js deleted file mode 100644 index 3d032f671..000000000 --- a/src/mathcommand.js +++ /dev/null @@ -1,41 +0,0 @@ -import { Command } from 'ckeditor5/src/core'; -import { getSelectedMathModelWidget } from './utils'; - -export default class MathCommand extends Command { - execute( equation, display, outputType, forceOutputType ) { - const model = this.editor.model; - const selection = model.document.selection; - const selectedElement = selection.getSelectedElement(); - - model.change( writer => { - let mathtex; - if ( selectedElement && ( selectedElement.is( 'element', 'mathtex-inline' ) || - selectedElement.is( 'element', 'mathtex-display' ) ) ) { - // Update selected element - const typeAttr = selectedElement.getAttribute( 'type' ); - - // Use already set type if found and is not forced - const type = forceOutputType ? outputType : typeAttr || outputType; - - mathtex = writer.createElement( display ? 'mathtex-display' : 'mathtex-inline', { equation, type, display } ); - } else { - // Create new model element - mathtex = writer.createElement( display ? 'mathtex-display' : 'mathtex-inline', { equation, type: outputType, display } ); - } - model.insertContent( mathtex ); - } ); - } - - refresh() { - const model = this.editor.model; - const selection = model.document.selection; - const selectedElement = selection.getSelectedElement(); - - this.isEnabled = selectedElement === null || ( selectedElement.is( 'element', 'mathtex-inline' ) || - selectedElement.is( 'element', 'mathtex-display' ) ); - - const selectedEquation = getSelectedMathModelWidget( selection ); - this.value = selectedEquation ? selectedEquation.getAttribute( 'equation' ) : null; - this.display = selectedEquation ? selectedEquation.getAttribute( 'display' ) : null; - } -} diff --git a/src/mathediting.js b/src/mathediting.js deleted file mode 100644 index 31d14f49a..000000000 --- a/src/mathediting.js +++ /dev/null @@ -1,214 +0,0 @@ -import MathCommand from './mathcommand'; -import { Plugin } from 'ckeditor5/src/core'; -import { toWidget, Widget, viewToModelPositionOutsideModelElement } from 'ckeditor5/src/widget'; -import { renderEquation, extractDelimiters } from './utils'; - -export default class MathEditing extends Plugin { - static get requires() { - return [ Widget ]; - } - - static get pluginName() { - return 'MathEditing'; - } - - constructor( editor ) { - super( editor ); - editor.config.define( 'math', { - engine: 'mathjax', - outputType: 'script', - className: 'math-tex', - forceOutputType: false, - enablePreview: true, - previewClassName: [], - popupClassName: [], - katexRenderOptions: {} - } ); - } - - init() { - const editor = this.editor; - editor.commands.add( 'math', new MathCommand( editor ) ); - - this._defineSchema(); - this._defineConverters(); - - editor.editing.mapper.on( - 'viewToModelPosition', - viewToModelPositionOutsideModelElement( editor.model, viewElement => viewElement.hasClass( 'math' ) ) - ); - } - - _defineSchema() { - const schema = this.editor.model.schema; - schema.register( 'mathtex-inline', { - allowWhere: '$text', - isInline: true, - isObject: true, - allowAttributes: [ 'equation', 'type', 'display' ] - } ); - - schema.register( 'mathtex-display', { - allowWhere: '$block', - isInline: false, - isObject: true, - allowAttributes: [ 'equation', 'type', 'display' ] - } ); - } - - _defineConverters() { - const conversion = this.editor.conversion; - const mathConfig = this.editor.config.get( 'math' ); - - // View -> Model - conversion.for( 'upcast' ) - // MathJax inline way (e.g. ) - .elementToElement( { - view: { - name: 'script', - attributes: { - type: 'math/tex' - } - }, - model: ( viewElement, { writer } ) => { - const equation = viewElement.getChild( 0 ).data.trim(); - return writer.createElement( 'mathtex-inline', { - equation, - type: mathConfig.forceOutputType ? mathConfig.outputType : 'script', - display: false - } ); - } - } ) - // MathJax display way (e.g. ) - .elementToElement( { - view: { - name: 'script', - attributes: { - type: 'math/tex; mode=display' - } - }, - model: ( viewElement, { writer } ) => { - const equation = viewElement.getChild( 0 ).data.trim(); - return writer.createElement( 'mathtex-display', { - equation, - type: mathConfig.forceOutputType ? mathConfig.outputType : 'script', - display: true - } ); - } - } ) - // CKEditor 4 way (e.g. \( \sqrt{\frac{a}{b}} \)) - .elementToElement( { - view: { - name: 'span', - classes: [ mathConfig.className ] - }, - model: ( viewElement, { writer } ) => { - const equation = viewElement.getChild( 0 ).data.trim(); - - const params = Object.assign( extractDelimiters( equation ), { - type: mathConfig.forceOutputType ? mathConfig.outputType : 'span' - } ); - - return writer.createElement( params.display ? 'mathtex-display' : 'mathtex-inline', params ); - } - } ) - // KaTeX from Quill: https://github.com/quilljs/quill/blob/develop/formats/formula.js - .elementToElement( { - view: { - name: 'span', - classes: [ 'ql-formula' ] - }, - model: ( viewElement, { writer } ) => { - const equation = viewElement.getAttribute( 'data-value' ).trim(); - return writer.createElement( 'mathtex-inline', { - equation, - type: mathConfig.forceOutputType ? mathConfig.outputType : 'script', - display: false - } ); - } - } ); - - // Model -> View (element) - conversion.for( 'editingDowncast' ) - .elementToElement( { - model: 'mathtex-inline', - view: ( modelItem, { writer } ) => { - const widgetElement = createMathtexEditingView( modelItem, writer ); - return toWidget( widgetElement, writer, 'span' ); - } - } ).elementToElement( { - model: 'mathtex-display', - view: ( modelItem, { writer } ) => { - const widgetElement = createMathtexEditingView( modelItem, writer ); - return toWidget( widgetElement, writer, 'div' ); - } - } ); - - // Model -> Data - conversion.for( 'dataDowncast' ) - .elementToElement( { - model: 'mathtex-inline', - view: createMathtexView - } ) - .elementToElement( { - model: 'mathtex-display', - view: createMathtexView - } ); - - // Create view for editor - function createMathtexEditingView( modelItem, writer ) { - const equation = modelItem.getAttribute( 'equation' ); - const display = modelItem.getAttribute( 'display' ); - - const styles = 'user-select: none; ' + ( display ? '' : 'display: inline-block;' ); - const classes = 'ck-math-tex ' + ( display ? 'ck-math-tex-display' : 'ck-math-tex-inline' ); - - const mathtexView = writer.createContainerElement( display ? 'div' : 'span', { - style: styles, - class: classes - } ); - - const uiElement = writer.createUIElement( 'div', null, function( domDocument ) { - const domElement = this.toDomElement( domDocument ); - - renderEquation( equation, domElement, mathConfig.engine, mathConfig.lazyLoad, display, false, mathConfig.previewClassName, - null, mathConfig.katexRenderOptions ); - - return domElement; - } ); - - writer.insert( writer.createPositionAt( mathtexView, 0 ), uiElement ); - - return mathtexView; - } - - // Create view for data - function createMathtexView( modelItem, { writer } ) { - const equation = modelItem.getAttribute( 'equation' ); - const type = modelItem.getAttribute( 'type' ); - const display = modelItem.getAttribute( 'display' ); - - if ( type === 'span' ) { - const mathtexView = writer.createContainerElement( 'span', { - class: mathConfig.className - } ); - - if ( display ) { - writer.insert( writer.createPositionAt( mathtexView, 0 ), writer.createText( '\\[' + equation + '\\]' ) ); - } else { - writer.insert( writer.createPositionAt( mathtexView, 0 ), writer.createText( '\\(' + equation + '\\)' ) ); - } - - return mathtexView; - } else { - const mathtexView = writer.createContainerElement( 'script', { - type: display ? 'math/tex; mode=display' : 'math/tex' - } ); - - writer.insert( writer.createPositionAt( mathtexView, 0 ), writer.createText( equation ) ); - - return mathtexView; - } - } - } -} diff --git a/src/mathui.js b/src/mathui.js deleted file mode 100644 index cba4f5d8e..000000000 --- a/src/mathui.js +++ /dev/null @@ -1,246 +0,0 @@ -import MathEditing from './mathediting'; -import MainFormView from './ui/mainformview'; -import mathIcon from '../theme/icons/math.svg'; -import { Plugin } from 'ckeditor5/src/core'; -import { ClickObserver } from 'ckeditor5/src/engine'; -import { ButtonView, ContextualBalloon, clickOutsideHandler } from 'ckeditor5/src/ui'; -import { global, uid } from 'ckeditor5/src/utils'; -import { getBalloonPositionData } from './utils'; - -const mathKeystroke = 'Ctrl+M'; - -export default class MathUI extends Plugin { - static get requires() { - return [ ContextualBalloon, MathEditing ]; - } - - static get pluginName() { - return 'MathUI'; - } - - init() { - const editor = this.editor; - editor.editing.view.addObserver( ClickObserver ); - - this._previewUid = `math-preview-${ uid() }`; - - this.formView = this._createFormView(); - - this._balloon = editor.plugins.get( ContextualBalloon ); - - this._createToolbarMathButton(); - - this._enableUserBalloonInteractions(); - } - - destroy() { - super.destroy(); - - this.formView.destroy(); - - // Destroy preview element - const previewEl = global.document.getElementById( this._previewUid ); - if ( previewEl ) { - previewEl.parentNode.removeChild( previewEl ); - } - } - - _showUI() { - const editor = this.editor; - const mathCommand = editor.commands.get( 'math' ); - - if ( !mathCommand.isEnabled ) { - return; - } - - this._addFormView(); - - this._balloon.showStack( 'main' ); - } - - _createFormView() { - const editor = this.editor; - const mathCommand = editor.commands.get( 'math' ); - - const mathConfig = editor.config.get( 'math' ); - - const formView = new MainFormView( - editor.locale, - mathConfig.engine, - mathConfig.lazyLoad, - mathConfig.enablePreview, - this._previewUid, - mathConfig.previewClassName, - mathConfig.popupClassName, - mathConfig.katexRenderOptions - ); - - formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' ); - formView.displayButtonView.bind( 'isOn' ).to( mathCommand, 'display' ); - - // Form elements should be read-only when corresponding commands are disabled. - formView.mathInputView.bind( 'isReadOnly' ).to( mathCommand, 'isEnabled', value => !value ); - formView.saveButtonView.bind( 'isEnabled' ).to( mathCommand ); - formView.displayButtonView.bind( 'isEnabled' ).to( mathCommand ); - - // Listen to submit button click - this.listenTo( formView, 'submit', () => { - editor.execute( 'math', formView.equation, formView.displayButtonView.isOn, mathConfig.outputType, mathConfig.forceOutputType ); - this._closeFormView(); - } ); - - // Listen to cancel button click - this.listenTo( formView, 'cancel', () => { - this._closeFormView(); - } ); - - // Close plugin ui, if esc is pressed (while ui is focused) - formView.keystrokes.set( 'esc', ( data, cancel ) => { - this._closeFormView(); - cancel(); - } ); - - return formView; - } - - _addFormView() { - if ( this._isFormInPanel ) { - return; - } - - const editor = this.editor; - const mathCommand = editor.commands.get( 'math' ); - - this._balloon.add( { - view: this.formView, - position: getBalloonPositionData( editor ) - } ); - - if ( this._balloon.visibleView === this.formView ) { - this.formView.mathInputView.fieldView.element.select(); - } - - // Show preview element - const previewEl = global.document.getElementById( this._previewUid ); - if ( previewEl && this.formView.previewEnabled ) { - // Force refresh preview - this.formView.mathView.updateMath(); - } - - this.formView.equation = mathCommand.value || ''; - this.formView.displayButtonView.isOn = mathCommand.display || false; - } - - _hideUI() { - if ( !this._isFormInPanel ) { - return; - } - - const editor = this.editor; - - this.stopListening( editor.ui, 'update' ); - this.stopListening( this._balloon, 'change:visibleView' ); - - editor.editing.view.focus(); - - // Remove form first because it's on top of the stack. - this._removeFormView(); - } - - _closeFormView() { - const mathCommand = this.editor.commands.get( 'math' ); - if ( mathCommand.value !== undefined ) { - this._removeFormView(); - } else { - this._hideUI(); - } - } - - _removeFormView() { - if ( this._isFormInPanel ) { - this.formView.saveButtonView.focus(); - - this._balloon.remove( this.formView ); - - // Hide preview element - const previewEl = global.document.getElementById( this._previewUid ); - if ( previewEl ) { - previewEl.style.visibility = 'hidden'; - } - - this.editor.editing.view.focus(); - } - } - - _createToolbarMathButton() { - const editor = this.editor; - const mathCommand = editor.commands.get( 'math' ); - const t = editor.t; - - // Handle the `Ctrl+M` keystroke and show the panel. - editor.keystrokes.set( mathKeystroke, ( keyEvtData, cancel ) => { - // Prevent focusing the search bar in FF and opening new tab in Edge. #153, #154. - cancel(); - - if ( mathCommand.isEnabled ) { - this._showUI(); - } - } ); - - this.editor.ui.componentFactory.add( 'math', locale => { - const button = new ButtonView( locale ); - - button.isEnabled = true; - button.label = t( 'Insert math' ); - button.icon = mathIcon; - button.keystroke = mathKeystroke; - button.tooltip = true; - button.isToggleable = true; - - button.bind( 'isEnabled' ).to( mathCommand, 'isEnabled' ); - - this.listenTo( button, 'execute', () => this._showUI() ); - - return button; - } ); - } - - _enableUserBalloonInteractions() { - const editor = this.editor; - const viewDocument = this.editor.editing.view.document; - this.listenTo( viewDocument, 'click', () => { - const mathCommand = editor.commands.get( 'math' ); - if ( mathCommand.value ) { - if ( mathCommand.isEnabled ) { - this._showUI(); - } - } - } ); - - // Close the panel on the Esc key press when the editable has focus and the balloon is visible. - editor.keystrokes.set( 'Esc', ( data, cancel ) => { - if ( this._isUIVisible ) { - this._hideUI(); - cancel(); - } - } ); - - // Close on click outside of balloon panel element. - clickOutsideHandler( { - emitter: this.formView, - activator: () => this._isFormInPanel, - contextElements: [ this._balloon.view.element ], - callback: () => this._hideUI() - } ); - } - - get _isUIVisible() { - const visibleView = this._balloon.visibleView; - - return visibleView == this.formView; - } - - get _isFormInPanel() { - return this._balloon.hasView( this.formView ); - } -} diff --git a/src/ui/mainformview.js b/src/ui/mainformview.js deleted file mode 100644 index 8f25efabb..000000000 --- a/src/ui/mainformview.js +++ /dev/null @@ -1,234 +0,0 @@ -import { icons } from 'ckeditor5/src/core'; -import { - ButtonView, createLabeledInputText, FocusCycler, LabelView, LabeledFieldView, - submitHandler, SwitchButtonView, View, ViewCollection -} from 'ckeditor5/src/ui'; -import { FocusTracker, KeystrokeHandler } from 'ckeditor5/src/utils'; -import { extractDelimiters, hasDelimiters } from '../utils'; -import MathView from './mathview'; -import '../../theme/mathform.css'; - -const { check: checkIcon, cancel: cancelIcon } = icons; - -export default class MainFormView extends View { - constructor( locale, engine, lazyLoad, previewEnabled, previewUid, previewClassName, popupClassName, katexRenderOptions ) { - super( locale ); - - const t = locale.t; - - // Create key event & focus trackers - this._createKeyAndFocusTrackers(); - - // Submit button - this.saveButtonView = this._createButton( t( 'Save' ), checkIcon, 'ck-button-save', null ); - this.saveButtonView.type = 'submit'; - - // Equation input - this.mathInputView = this._createMathInput(); - - // Display button - this.displayButtonView = this._createDisplayButton(); - - // Cancel button - this.cancelButtonView = this._createButton( t( 'Cancel' ), cancelIcon, 'ck-button-cancel', 'cancel' ); - - this.previewEnabled = previewEnabled; - - let children = []; - if ( this.previewEnabled ) { - // Preview label - this.previewLabel = new LabelView( locale ); - this.previewLabel.text = t( 'Equation preview' ); - - // Math element - this.mathView = new MathView( engine, lazyLoad, locale, previewUid, previewClassName, katexRenderOptions ); - this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' ); - - children = [ - this.mathInputView, - this.displayButtonView, - this.previewLabel, - this.mathView - ]; - } else { - children = [ - this.mathInputView, - this.displayButtonView - ]; - } - - // Add UI elements to template - this.setTemplate( { - tag: 'form', - attributes: { - class: [ - 'ck', - 'ck-math-form', - ...popupClassName - ], - tabindex: '-1', - spellcheck: 'false' - }, - children: [ - { - tag: 'div', - attributes: { - class: [ - 'ck-math-view' - ] - }, - children - }, - this.saveButtonView, - this.cancelButtonView - ] - } ); - } - - render() { - super.render(); - - // Prevent default form submit event & trigger custom 'submit' - submitHandler( { - view: this - } ); - - // Register form elements to focusable elements - const childViews = [ - this.mathInputView, - this.displayButtonView, - this.saveButtonView, - this.cancelButtonView - ]; - - childViews.forEach( v => { - this._focusables.add( v ); - this.focusTracker.add( v.element ); - } ); - - // Listen to keypresses inside form element - this.keystrokes.listenTo( this.element ); - } - - focus() { - this._focusCycler.focusFirst(); - } - - get equation() { - return this.mathInputView.fieldView.element.value; - } - - set equation( equation ) { - this.mathInputView.fieldView.element.value = equation; - if ( this.previewEnabled ) { - this.mathView.value = equation; - } - } - - _createKeyAndFocusTrackers() { - this.focusTracker = new FocusTracker(); - this.keystrokes = new KeystrokeHandler(); - this._focusables = new ViewCollection(); - - this._focusCycler = new FocusCycler( { - focusables: this._focusables, - focusTracker: this.focusTracker, - keystrokeHandler: this.keystrokes, - actions: { - focusPrevious: 'shift + tab', - focusNext: 'tab' - } - } ); - } - - _createMathInput() { - const t = this.locale.t; - - // Create equation input - const mathInput = new LabeledFieldView( this.locale, createLabeledInputText ); - const fieldView = mathInput.fieldView; - mathInput.infoText = t( 'Insert equation in TeX format.' ); - - const onInput = () => { - if ( fieldView.element != null ) { - let equationInput = fieldView.element.value.trim(); - - // If input has delimiters - if ( hasDelimiters( equationInput ) ) { - // Get equation without delimiters - const params = extractDelimiters( equationInput ); - - // Remove delimiters from input field - fieldView.element.value = params.equation; - - equationInput = params.equation; - - // update display button and preview - this.displayButtonView.isOn = params.display; - } - if ( this.previewEnabled ) { - // Update preview view - this.mathView.value = equationInput; - } - - this.saveButtonView.isEnabled = !!equationInput; - } - }; - - fieldView.on( 'render', onInput ); - fieldView.on( 'input', onInput ); - - return mathInput; - } - - _createButton( label, icon, className, eventName ) { - const button = new ButtonView( this.locale ); - - button.set( { - label, - icon, - tooltip: true - } ); - - button.extendTemplate( { - attributes: { - class: className - } - } ); - - if ( eventName ) { - button.delegate( 'execute' ).to( this, eventName ); - } - - return button; - } - - _createDisplayButton() { - const t = this.locale.t; - - const switchButton = new SwitchButtonView( this.locale ); - - switchButton.set( { - label: t( 'Display mode' ), - withText: true - } ); - - switchButton.extendTemplate( { - attributes: { - class: 'ck-button-display-toggle' - } - } ); - - switchButton.on( 'execute', () => { - // Toggle state - switchButton.isOn = !switchButton.isOn; - - if ( this.previewEnabled ) { - // Update preview view - this.mathView.display = switchButton.isOn; - } - } ); - - return switchButton; - } -} diff --git a/src/ui/mathview.js b/src/ui/mathview.js deleted file mode 100644 index 0badcd37b..000000000 --- a/src/ui/mathview.js +++ /dev/null @@ -1,43 +0,0 @@ -import { View } from 'ckeditor5/src/ui'; -import { renderEquation } from '../utils'; - -export default class MathView extends View { - constructor( engine, lazyLoad, locale, previewUid, previewClassName, katexRenderOptions ) { - super( locale ); - - this.engine = engine; - this.lazyLoad = lazyLoad; - this.previewUid = previewUid; - this.katexRenderOptions = katexRenderOptions; - this.previewClassName = previewClassName; - - this.set( 'value', '' ); - this.set( 'display', false ); - - this.on( 'change', () => { - if ( this.isRendered ) { - this.updateMath(); - } - } ); - - this.setTemplate( { - tag: 'div', - attributes: { - class: [ - 'ck', - 'ck-math-preview' - ] - } - } ); - } - - updateMath() { - renderEquation( this.value, this.element, this.engine, this.lazyLoad, this.display, true, this.previewUid, this.previewClassName, - this.katexRenderOptions ); - } - - render() { - super.render(); - this.updateMath(); - } -} diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index f4aed49a1..000000000 --- a/src/utils.js +++ /dev/null @@ -1,233 +0,0 @@ -import { BalloonPanelView } from 'ckeditor5/src/ui'; -import { global } from 'ckeditor5/src/utils'; - -export function getSelectedMathModelWidget( selection ) { - const selectedElement = selection.getSelectedElement(); - - if ( selectedElement && ( selectedElement.is( 'element', 'mathtex-inline' ) || selectedElement.is( 'element', 'mathtex-display' ) ) ) { - return selectedElement; - } - - return null; -} - -// Simple MathJax 3 version check -export function isMathJaxVersion3( version ) { - return version && typeof version === 'string' && version.split( '.' ).length === 3 && version.split( '.' )[ 0 ] === '3'; -} - -// Check if equation has delimiters. -export function hasDelimiters( text ) { - return text.match( /^(\\\[.*?\\\]|\\\(.*?\\\))$/ ); -} - -// Find delimiters count -export function delimitersCounts( text ) { - return text.match( /(\\\[|\\\]|\\\(|\\\))/g ).length; -} - -// Extract delimiters and figure display mode for the model -export function extractDelimiters( equation ) { - equation = equation.trim(); - - // Remove delimiters (e.g. \( \) or \[ \]) - const hasInlineDelimiters = equation.includes( '\\(' ) && equation.includes( '\\)' ); - const hasDisplayDelimiters = equation.includes( '\\[' ) && equation.includes( '\\]' ); - if ( hasInlineDelimiters || hasDisplayDelimiters ) { - equation = equation.substring( 2, equation.length - 2 ).trim(); - } - - return { - equation, - display: hasDisplayDelimiters - }; -} - -export async function renderEquation( - equation, element, engine = 'katex', lazyLoad, display = false, preview = false, previewUid, previewClassName = [], - katexRenderOptions = {} -) { - if ( engine === 'mathjax' && typeof MathJax !== 'undefined' ) { - if ( isMathJaxVersion3( MathJax.version ) ) { - selectRenderMode( element, preview, previewUid, previewClassName, el => { - renderMathJax3( equation, el, display, () => { - if ( preview ) { - moveAndScaleElement( element, el ); - el.style.visibility = 'visible'; - } - } ); - } ); - } else { - selectRenderMode( element, preview, previewUid, previewClassName, el => { - // Fixme: MathJax typesetting cause occasionally math processing error without asynchronous call - global.window.setTimeout( () => { - renderMathJax2( equation, el, display ); - - // Move and scale after rendering - if ( preview ) { - // eslint-disable-next-line - MathJax.Hub.Queue( () => { - moveAndScaleElement( element, el ); - el.style.visibility = 'visible'; - } ); - } - } ); - } ); - } - } else if ( engine === 'katex' && typeof katex !== 'undefined' ) { - selectRenderMode( element, preview, previewUid, previewClassName, el => { - katex.render( equation, el, { - throwOnError: false, - displayMode: display, - ...katexRenderOptions - } ); - if ( preview ) { - moveAndScaleElement( element, el ); - el.style.visibility = 'visible'; - } - } ); - } else if ( typeof engine === 'function' ) { - engine( equation, element, display ); - } else { - if ( typeof lazyLoad !== 'undefined' ) { - try { - if ( !global.window.CKEDITOR_MATH_LAZY_LOAD ) { - global.window.CKEDITOR_MATH_LAZY_LOAD = lazyLoad(); - } - element.innerHTML = equation; - await global.window.CKEDITOR_MATH_LAZY_LOAD; - renderEquation( equation, element, engine, undefined, display, preview, previewUid, previewClassName, katexRenderOptions ); - } - catch ( err ) { - element.innerHTML = equation; - console.error( `math-tex-typesetting-lazy-load-failed: Lazy load failed: ${ err }` ); - } - } else { - element.innerHTML = equation; - console.warn( `math-tex-typesetting-missing: Missing the mathematical typesetting engine (${ engine }) for tex.` ); - } - } -} - -export function getBalloonPositionData( editor ) { - const view = editor.editing.view; - const defaultPositions = BalloonPanelView.defaultPositions; - - const selectedElement = view.document.selection.getSelectedElement(); - if ( selectedElement ) { - return { - target: view.domConverter.viewToDom( selectedElement ), - positions: [ - defaultPositions.southArrowNorth, - defaultPositions.southArrowNorthWest, - defaultPositions.southArrowNorthEast - ] - }; - } - else { - const viewDocument = view.document; - return { - target: view.domConverter.viewRangeToDom( viewDocument.selection.getFirstRange() ), - positions: [ - defaultPositions.southArrowNorth, - defaultPositions.southArrowNorthWest, - defaultPositions.southArrowNorthEast - ] - }; - } -} - -function selectRenderMode( element, preview, previewUid, previewClassName, cb ) { - if ( preview ) { - createPreviewElement( element, previewUid, previewClassName, previewEl => { - cb( previewEl ); - } ); - } else { - cb( element ); - } -} - -function renderMathJax3( equation, element, display, cb ) { - let promiseFunction = undefined; - if ( typeof MathJax.tex2chtmlPromise !== 'undefined' ) { - promiseFunction = MathJax.tex2chtmlPromise; - } else if ( typeof MathJax.tex2svgPromise !== 'undefined' ) { - promiseFunction = MathJax.tex2svgPromise; - } - - if ( typeof promiseFunction !== 'undefined' ) { - promiseFunction( equation, { display } ).then( node => { - if ( element.firstChild ) { - element.removeChild( element.firstChild ); - } - element.appendChild( node ); - cb(); - } ); - } -} - -function renderMathJax2( equation, element, display ) { - if ( display ) { - element.innerHTML = '\\[' + equation + '\\]'; - } else { - element.innerHTML = '\\(' + equation + '\\)'; - } - // eslint-disable-next-line - MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, element ] ); -} - -function createPreviewElement( element, previewUid, previewClassName, render ) { - const previewEl = getPreviewElement( element, previewUid, previewClassName ); - render( previewEl ); -} - -function getPreviewElement( element, previewUid, previewClassName ) { - let previewEl = global.document.getElementById( previewUid ); - // Create if not found - if ( !previewEl ) { - previewEl = global.document.createElement( 'div' ); - previewEl.setAttribute( 'id', previewUid ); - previewEl.classList.add( ...previewClassName ); - previewEl.style.visibility = 'hidden'; - global.document.body.appendChild( previewEl ); - - let ticking = false; - - const renderTransformation = () => { - if ( !ticking ) { - global.window.requestAnimationFrame( () => { - moveElement( element, previewEl ); - ticking = false; - } ); - - ticking = true; - } - }; - - // Create scroll listener for following - global.window.addEventListener( 'resize', renderTransformation ); - global.window.addEventListener( 'scroll', renderTransformation ); - } - return previewEl; -} - -function moveAndScaleElement( parent, child ) { - // Move to right place - moveElement( parent, child ); - - // Scale parent element same as preview - const domRect = child.getBoundingClientRect(); - parent.style.width = domRect.width + 'px'; - parent.style.height = domRect.height + 'px'; -} - -function moveElement( parent, child ) { - const domRect = parent.getBoundingClientRect(); - const left = global.window.scrollX + domRect.left; - const top = global.window.scrollY + domRect.top; - child.style.position = 'absolute'; - child.style.left = left + 'px'; - child.style.top = top + 'px'; - child.style.zIndex = 'var(--ck-z-panel)'; - child.style.pointerEvents = 'none'; -}