mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-09-22 20:28:07 +08:00
Add automath and fix preview and display button
This commit is contained in:
parent
eed45a5dd3
commit
6e08776268
89
README.md
89
README.md
@ -23,8 +23,7 @@ Add it to builtinPlugins
|
|||||||
```js
|
```js
|
||||||
InlineEditor.builtinPlugins = [
|
InlineEditor.builtinPlugins = [
|
||||||
// ...
|
// ...
|
||||||
Mathematics,
|
Mathematics
|
||||||
// ...
|
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -35,8 +34,7 @@ InlineEditor.defaultConfig = {
|
|||||||
toolbar: {
|
toolbar: {
|
||||||
items: [
|
items: [
|
||||||
// ...
|
// ...
|
||||||
'math',
|
'math'
|
||||||
// ...
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// ...
|
// ...
|
||||||
@ -44,14 +42,56 @@ InlineEditor.defaultConfig = {
|
|||||||
engine: 'mathjax',
|
engine: 'mathjax',
|
||||||
outputType: 'script',
|
outputType: 'script',
|
||||||
forceOutputType: false
|
forceOutputType: false
|
||||||
},
|
}
|
||||||
// ...
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
### Copying plugin's theme for Lark
|
### 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.
|
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:
|
Supported input and output formats are:
|
||||||
```html
|
```html
|
||||||
<!-- MathJax style http://docs.mathjax.org/en/v2.7-latest/advanced/model.html#how-mathematics-is-stored-in-the-page -->
|
<!-- 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>
|
<span class="math-tex">\[ \sqrt{\frac{a}{b}} \]</span>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Available typesetting engines
|
### Math paste support
|
||||||
### MathJax
|
It's possible to paste equations in mathtex by using __delimiters__. For example:
|
||||||
- Tested by using version __2.7.5__ and __TeX-MML-AM_HTMLorMML__ configuration. It works also with version __3.0.0__ or newer!
|
- __\\(__ x=\frac{-b\pm\sqrt{b^2-4ac}}{2a} __\\)__ (inline mode)
|
||||||
- Use __\\( \\)__ delimiters for inline and __\\[ \\]__ delimiters for display
|
- __\\[__ x=\frac{-b\pm\sqrt{b^2-4ac}}{2a} __\\]__ (display mode)
|
||||||
### 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
|
|
||||||
|
|
||||||
## Todo
|
## Todo
|
||||||
- AutoMath like AutoMediaEmbed
|
- Convert equations to mathtex when paste document from word
|
||||||
- Convert equation to mathtex when paste from word
|
|
||||||
- Fix KaTex preview
|
- Fix KaTex preview
|
||||||
- Make better way to import lark theme for plugin
|
- Make better way to import lark theme for plugin
|
||||||
- MathML input and output when using MathJax version 3
|
- MathML input and output when using MathJax version 3
|
||||||
|
125
src/automath.js
Normal file
125
src/automath.js
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
@ -3,10 +3,11 @@ import Widget from '@ckeditor/ckeditor5-widget/src/widget';
|
|||||||
|
|
||||||
import MathUI from './mathui';
|
import MathUI from './mathui';
|
||||||
import MathEditing from './mathediting';
|
import MathEditing from './mathediting';
|
||||||
|
import AutoMath from './automath';
|
||||||
|
|
||||||
export default class Math extends Plugin {
|
export default class Math extends Plugin {
|
||||||
static get requires() {
|
static get requires() {
|
||||||
return [ MathEditing, MathUI, Widget ];
|
return [ MathEditing, MathUI, Widget, AutoMath ];
|
||||||
}
|
}
|
||||||
|
|
||||||
static get pluginName() {
|
static get pluginName() {
|
||||||
|
@ -5,7 +5,7 @@ import Widget from '@ckeditor/ckeditor5-widget/src/widget';
|
|||||||
|
|
||||||
import MathCommand from './mathcommand';
|
import MathCommand from './mathcommand';
|
||||||
|
|
||||||
import { defaultConfig, renderEquation } from './utils';
|
import { defaultConfig, renderEquation, removeDelimiters } from './utils';
|
||||||
|
|
||||||
export default class MathEditing extends Plugin {
|
export default class MathEditing extends Plugin {
|
||||||
static get requires() {
|
static get requires() {
|
||||||
@ -105,20 +105,14 @@ export default class MathEditing extends Plugin {
|
|||||||
classes: [ 'math-tex' ]
|
classes: [ 'math-tex' ]
|
||||||
},
|
},
|
||||||
model: ( viewElement, modelWriter ) => {
|
model: ( viewElement, modelWriter ) => {
|
||||||
let equation = viewElement.getChild( 0 ).data.trim();
|
const equation = viewElement.getChild( 0 ).data.trim();
|
||||||
|
|
||||||
// Remove delimiters (e.g. \( \) or \[ \])
|
const params = {
|
||||||
const hasInlineDelimiters = equation.includes( '\\(' ) && equation.includes( '\\)' );
|
...removeDelimiters( equation ),
|
||||||
const hasDisplayDelimiters = equation.includes( '\\[' ) && equation.includes( '\\]' );
|
type: mathConfig.forceOutputType ? mathConfig.outputType : 'span'
|
||||||
if ( hasInlineDelimiters || hasDisplayDelimiters ) {
|
};
|
||||||
equation = equation.substring( 2, equation.length - 2 ).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
return modelWriter.createElement( 'mathtex', {
|
return modelWriter.createElement( 'mathtex', params );
|
||||||
equation,
|
|
||||||
type: mathConfig.forceOutputType ? mathConfig.outputType : 'span',
|
|
||||||
display: hasDisplayDelimiters
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ export default class MathUI extends Plugin {
|
|||||||
const formView = new MainFormView( editor.locale, mathConfig.engine );
|
const formView = new MainFormView( editor.locale, mathConfig.engine );
|
||||||
|
|
||||||
formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' );
|
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
|
// Listen to submit button click
|
||||||
this.listenTo( formView, 'submit', () => {
|
this.listenTo( formView, 'submit', () => {
|
||||||
|
@ -15,6 +15,8 @@ import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg';
|
|||||||
|
|
||||||
import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler';
|
import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler';
|
||||||
|
|
||||||
|
import { removeDelimiters, EQUATION_REGEXP } from '../utils';
|
||||||
|
|
||||||
import MathView from './mathview';
|
import MathView from './mathview';
|
||||||
|
|
||||||
import '../../theme/mathform.css';
|
import '../../theme/mathform.css';
|
||||||
@ -53,6 +55,7 @@ export default class MainFormView extends View {
|
|||||||
|
|
||||||
// Math element
|
// Math element
|
||||||
this.mathView = new MathView( engine, locale );
|
this.mathView = new MathView( engine, locale );
|
||||||
|
this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' );
|
||||||
|
|
||||||
children = [
|
children = [
|
||||||
this.mathInputView,
|
this.mathInputView,
|
||||||
@ -160,7 +163,25 @@ export default class MainFormView extends View {
|
|||||||
mathInput.infoText = t( 'Insert equation in TeX format.' );
|
mathInput.infoText = t( 'Insert equation in TeX format.' );
|
||||||
inputView.on( 'input', () => {
|
inputView.on( 'input', () => {
|
||||||
if ( this.previewEnabled ) {
|
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', () => {
|
switchButton.on( 'execute', () => {
|
||||||
// Toggle state
|
// Toggle state
|
||||||
this.set( 'displayIsOn', !this.displayIsOn );
|
switchButton.isOn = !switchButton.isOn;
|
||||||
|
|
||||||
if ( this.previewEnabled ) {
|
if ( this.previewEnabled ) {
|
||||||
// Update preview view
|
// Update preview view
|
||||||
this.mathView.display = this.displayIsOn;
|
this.mathView.display = switchButton.isOn;
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
34
src/utils.js
34
src/utils.js
@ -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 ) {
|
export function renderEquation( equation, element, engine = 'katex', display = false ) {
|
||||||
|
if ( !element ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
if ( engine === 'mathjax' && typeof MathJax !== 'undefined' ) {
|
if ( engine === 'mathjax' && typeof MathJax !== 'undefined' ) {
|
||||||
const version = MathJax.version;
|
const version = MathJax.version;
|
||||||
@ -22,7 +33,10 @@ export function renderEquation( equation, element, engine = 'katex', display = f
|
|||||||
} else {
|
} else {
|
||||||
element.innerHTML = '\\(' + equation + '\\)';
|
element.innerHTML = '\\(' + equation + '\\)';
|
||||||
}
|
}
|
||||||
|
// Fixme: MathJax render occasionally math processing error without timeout
|
||||||
|
setTimeout( () => {
|
||||||
MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, element ] );
|
MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, element ] );
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
} else if ( engine === 'katex' && typeof katex !== 'undefined' ) {
|
} else if ( engine === 'katex' && typeof katex !== 'undefined' ) {
|
||||||
katex.render( equation, element, {
|
katex.render( equation, element, {
|
||||||
@ -38,6 +52,7 @@ export function renderEquation( equation, element, engine = 'katex', display = f
|
|||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simple MathJax 3 version check
|
||||||
export function isMathJaxVersion3( version ) {
|
export function isMathJaxVersion3( version ) {
|
||||||
return version && typeof version === 'string' && version.split( '.' ).length === 3 && version.split( '.' )[ 0 ] === '3';
|
return version && typeof version === 'string' && version.split( '.' ).length === 3 && version.split( '.' )[ 0 ] === '3';
|
||||||
}
|
}
|
||||||
@ -52,8 +67,19 @@ export function getSelectedMathModelWidget( selection ) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultConfig = {
|
// Remove delimiters and figure display mode for the model
|
||||||
engine: 'mathjax',
|
export function removeDelimiters( equation ) {
|
||||||
outputType: 'script',
|
equation = equation.trim();
|
||||||
forceOutputType: false
|
|
||||||
|
// 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
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user