Add automath and fix preview and display button

This commit is contained in:
Sauli Anto 2019-09-28 18:03:05 +03:00
parent eed45a5dd3
commit 6e08776268
7 changed files with 243 additions and 63 deletions

View File

@ -23,8 +23,7 @@ Add it to builtinPlugins
```js
InlineEditor.builtinPlugins = [
// ...
Mathematics,
// ...
Mathematics
];
```
@ -35,8 +34,7 @@ InlineEditor.defaultConfig = {
toolbar: {
items: [
// ...
'math',
// ...
'math'
]
},
// ...
@ -44,14 +42,56 @@ InlineEditor.defaultConfig = {
engine: 'mathjax',
outputType: 'script',
forceOutputType: false
},
// ...
}
};
```
### Copying plugin's theme for Lark
Copy __theme/ckeditor5-math__ folder from [https://github.com/isaul32/ckeditor5-theme-lark](https://github.com/isaul32/ckeditor5-theme-lark) to your lark theme repository fork.
## Supported input and output formats
### Styles
Styles requires PostCSS like offical CKEditor plugins.
## Configuration & Usage
### Available typesetting engines
__MathJax__
- Tested by using version __2.7.5__ and __TeX-MML-AM_HTMLorMML__ configuration. It works also with version __3.0.0__ or newer!
- Use __\\( \\)__ delimiters for inline and __\\[ \\]__ delimiters for display
__KaTeX__
- Tested by using version __0.11.0__
- Doesn't support preview feature because __.ck-reset_all *__ css rules from ckeditor5-ui and ckeditor5-theme-lark breaks it.
__Custom__
It's possible to implement with engine config. For example, this feature can be used when use back end rendering.
```js
InlineEditor.defaultConfig = {
// ...
math: {
engine: (equation, element, display) => {
// ...
}
}
}
```
- __equation__ is equation in TeX format without delimiters
- __element__ is DOM element reserved for rendering
- __display__ is boolean. Typesetting should be inline when false
### Plugin options
```js
InlineEditor.defaultConfig = {
// ...
math: {
engine: 'mathjax', // or katex or function (equation, element, display) => { ... }
outputType: 'script', // or span or math
forceOutputType: false // forces output to use outputType
}
}
```
### Supported input and output formats
Supported input and output formats are:
```html
<!-- MathJax style http://docs.mathjax.org/en/v2.7-latest/advanced/model.html#how-mathematics-is-stored-in-the-page -->
@ -63,38 +103,13 @@ Supported input and output formats are:
<span class="math-tex">\[ \sqrt{\frac{a}{b}} \]</span>
```
## Available typesetting engines
### MathJax
- Tested by using version __2.7.5__ and __TeX-MML-AM_HTMLorMML__ configuration. It works also with version __3.0.0__ or newer!
- Use __\\( \\)__ delimiters for inline and __\\[ \\]__ delimiters for display
### KaTeX
- Tested by using version __0.11.0__
- Doesn't support preview feature because __.ck-reset_all *__ css rules from ckeditor5-ui and ckeditor5-theme-lark breaks it.
### Custom
It's possible to implement with _engine: (__equation__, __element__, __display__) => { ... }_ math config.
- __equation__ is equation in TeX format without delimiters
- __element__ is DOM element reserved for rendering
- __display__ is boolean. Typesetting should be inline when false
## Plugin options
```js
InlineEditor.defaultConfig = {
// ...
math: {
engine: 'mathjax', // or katex or function (equation, element, display) => { ... }
outputType: 'script', // or span or math
forceOutputType: false // forces output to use outputType
}
// ...
}
```
## Styles
- Styles requires PostCSS like offical CKEditor plugins
### Math paste support
It's possible to paste equations in mathtex by using __delimiters__. For example:
- __\\(__ x=\frac{-b\pm\sqrt{b^2-4ac}}{2a} __\\)__ (inline mode)
- __\\[__ x=\frac{-b\pm\sqrt{b^2-4ac}}{2a} __\\]__ (display mode)
## Todo
- AutoMath like AutoMediaEmbed
- Convert equation to mathtex when paste from word
- Convert equations to mathtex when paste document from word
- Fix KaTex preview
- Make better way to import lark theme for plugin
- MathML input and output when using MathJax version 3

125
src/automath.js Normal file
View File

@ -0,0 +1,125 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
import Undo from '@ckeditor/ckeditor5-undo/src/undo';
import LiveRange from '@ckeditor/ckeditor5-engine/src/model/liverange';
import LivePosition from '@ckeditor/ckeditor5-engine/src/model/liveposition';
import { defaultConfig, removeDelimiters, EQUATION_REGEXP } 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 ); // eslint-disable-line
this._positionToInsert.detach();
this._timeoutId = null;
this._positionToInsert = null;
}
}, { priority: 'high' } );
}
_mathBetweenPositions( leftPosition, rightPosition ) {
const editor = this.editor;
const mathConfig = {
...defaultConfig,
...this.editor.config.get( 'math' )
};
const equationRange = new LiveRange( leftPosition, rightPosition );
const walker = equationRange.getWalker( { ignoreElementEnd: true } );
let equation = '';
// Get equation text
for ( const node of walker ) {
if ( node.item.is( 'textProxy' ) ) {
equation += node.item.data;
}
}
equation = equation.trim();
// Check if equation
if ( !equation.match( EQUATION_REGEXP ) ) {
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( () => { // eslint-disable-line
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( writer => {
const params = {
...removeDelimiters( equation ),
type: mathConfig.outputType,
};
const mathElement = writer.createElement( 'mathtex', params );
editor.model.insertContent( mathElement, insertPosition );
writer.setSelection( mathElement, 'on' );
} );
this._positionToInsert.detach();
this._positionToInsert = null;
} );
}, 100 );
}
}

View File

@ -3,10 +3,11 @@ import Widget from '@ckeditor/ckeditor5-widget/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, Widget ];
return [ MathEditing, MathUI, Widget, AutoMath ];
}
static get pluginName() {

View File

@ -5,7 +5,7 @@ import Widget from '@ckeditor/ckeditor5-widget/src/widget';
import MathCommand from './mathcommand';
import { defaultConfig, renderEquation } from './utils';
import { defaultConfig, renderEquation, removeDelimiters } from './utils';
export default class MathEditing extends Plugin {
static get requires() {
@ -105,20 +105,14 @@ export default class MathEditing extends Plugin {
classes: [ 'math-tex' ]
},
model: ( viewElement, modelWriter ) => {
let equation = viewElement.getChild( 0 ).data.trim();
const equation = viewElement.getChild( 0 ).data.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();
}
const params = {
...removeDelimiters( equation ),
type: mathConfig.forceOutputType ? mathConfig.outputType : 'span'
};
return modelWriter.createElement( 'mathtex', {
equation,
type: mathConfig.forceOutputType ? mathConfig.outputType : 'span',
display: hasDisplayDelimiters
} );
return modelWriter.createElement( 'mathtex', params );
}
} );

View File

@ -67,7 +67,7 @@ export default class MathUI extends Plugin {
const formView = new MainFormView( editor.locale, mathConfig.engine );
formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' );
formView.displayButtonView.bind( 'displayIsOn' ).to( mathCommand, 'display' );
formView.displayButtonView.bind( 'isOn' ).to( mathCommand, 'display' );
// Listen to submit button click
this.listenTo( formView, 'submit', () => {

View File

@ -15,6 +15,8 @@ import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg';
import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler';
import { removeDelimiters, EQUATION_REGEXP } from '../utils';
import MathView from './mathview';
import '../../theme/mathform.css';
@ -53,6 +55,7 @@ export default class MainFormView extends View {
// Math element
this.mathView = new MathView( engine, locale );
this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' );
children = [
this.mathInputView,
@ -160,7 +163,25 @@ export default class MainFormView extends View {
mathInput.infoText = t( 'Insert equation in TeX format.' );
inputView.on( 'input', () => {
if ( this.previewEnabled ) {
this.mathView.value = inputView.element.value;
const equationInput = inputView.element.value.trim();
// If input has delimiters
if ( equationInput.match( EQUATION_REGEXP ) ) {
// Get equation without delimiters
const params = removeDelimiters( equationInput );
// Remove delimiters from input field
inputView.element.value = params.equation;
// update display button and preview
this.displayButtonView.isOn = params.display;
if ( this.previewEnabled ) {
// Update preview view
this.mathView.value = params.equation;
}
} else {
this.mathView.value = equationInput;
}
}
} );
@ -205,15 +226,13 @@ export default class MainFormView extends View {
}
} );
switchButton.bind( 'isOn' ).to( this, 'displayIsOn' );
switchButton.on( 'execute', () => {
// Toggle state
this.set( 'displayIsOn', !this.displayIsOn );
switchButton.isOn = !switchButton.isOn;
if ( this.previewEnabled ) {
// Update preview view
this.mathView.display = this.displayIsOn;
this.mathView.display = switchButton.isOn;
}
} );

View File

@ -1,4 +1,15 @@
export const EQUATION_REGEXP = /^\\(\[|\().*?\\(\]|\))$/;
export const defaultConfig = {
engine: 'mathjax',
outputType: 'script',
forceOutputType: false
};
export function renderEquation( equation, element, engine = 'katex', display = false ) {
if ( !element ) {
return;
}
/* eslint-disable */
if ( engine === 'mathjax' && typeof MathJax !== 'undefined' ) {
const version = MathJax.version;
@ -22,7 +33,10 @@ export function renderEquation( equation, element, engine = 'katex', display = f
} else {
element.innerHTML = '\\(' + equation + '\\)';
}
MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, element ] );
// Fixme: MathJax render occasionally math processing error without timeout
setTimeout( () => {
MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, element ] );
}, 100);
}
} else if ( engine === 'katex' && typeof katex !== 'undefined' ) {
katex.render( equation, element, {
@ -38,6 +52,7 @@ export function renderEquation( equation, element, engine = 'katex', display = f
/* eslint-enable */
}
// Simple MathJax 3 version check
export function isMathJaxVersion3( version ) {
return version && typeof version === 'string' && version.split( '.' ).length === 3 && version.split( '.' )[ 0 ] === '3';
}
@ -52,8 +67,19 @@ export function getSelectedMathModelWidget( selection ) {
return null;
}
export const defaultConfig = {
engine: 'mathjax',
outputType: 'script',
forceOutputType: false
};
// Remove delimiters and figure display mode for the model
export function removeDelimiters( 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
};
}