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
|
||||
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
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 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() {
|
||||
|
@ -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 );
|
||||
}
|
||||
} );
|
||||
|
||||
|
@ -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', () => {
|
||||
|
@ -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;
|
||||
}
|
||||
} );
|
||||
|
||||
|
36
src/utils.js
36
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 ) {
|
||||
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 + '\\)';
|
||||
}
|
||||
// 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
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user