mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 19:12:27 +08:00
Implement plugin
This commit is contained in:
commit
13a10dcfdd
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
7
.eslintrc.js
Normal file
7
.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
/* eslint-env node */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: 'ckeditor5'
|
||||
};
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules/
|
5
LICENSE
Normal file
5
LICENSE
Normal file
@ -0,0 +1,5 @@
|
||||
Copyright 2019 isaul32
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# ckeditor5-math
|
||||
|
||||
## Supported input and output formats
|
||||
- <script type="math/tex">\sqrt{\frac{a}{b}}</script>
|
||||
- <script type="math/tex; mode=display">\sqrt{\frac{a}{b}}</script>
|
||||
- <span class="math-tex">\( \sqrt{\frac{a}{b}} \)</span>
|
||||
- <span class="math-tex">\[ \sqrt{\frac{a}{b}} \]</span>
|
||||
|
||||
## Styles
|
||||
- Styles requires PostCSS
|
3041
package-lock.json
generated
Normal file
3041
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
package.json
Normal file
50
package.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "ckeditor5-math",
|
||||
"version": "1.0.0",
|
||||
"description": "Math feature for CKEditor 5.",
|
||||
"keywords": [
|
||||
"ckeditor",
|
||||
"ckeditor5",
|
||||
"ckeditor 5",
|
||||
"ckeditor5-feature",
|
||||
"ckeditor5-plugin"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ckeditor/ckeditor5-core": "^12.3.0",
|
||||
"@ckeditor/ckeditor5-ui": "^14.0.0",
|
||||
"@ckeditor/ckeditor5-utils": "^14.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-editor-inline": "^12.3.0",
|
||||
"@ckeditor/ckeditor5-essentials": "^11.0.5",
|
||||
"eslint": "^5.5.0",
|
||||
"eslint-config-ckeditor5": "^2.0.0",
|
||||
"husky": "^1.3.1",
|
||||
"lint-staged": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0",
|
||||
"npm": ">=5.7.1"
|
||||
},
|
||||
"author": "isaul32",
|
||||
"license": "ISC",
|
||||
"files": [
|
||||
"lang",
|
||||
"src",
|
||||
"theme"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint --quiet **/*.js",
|
||||
"lint:fix": "eslint --quiet **/*.js --fix"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.js": [
|
||||
"eslint --quiet"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
}
|
||||
}
|
15
src/math.js
Normal file
15
src/math.js
Normal file
@ -0,0 +1,15 @@
|
||||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
||||
import Widget from '@ckeditor/ckeditor5-widget/src/widget';
|
||||
|
||||
import MathUI from './mathui';
|
||||
import MathEditing from './mathediting';
|
||||
|
||||
export default class Math extends Plugin {
|
||||
static get requires() {
|
||||
return [ MathEditing, MathUI, Widget ];
|
||||
}
|
||||
|
||||
static get pluginName() {
|
||||
return 'Math';
|
||||
}
|
||||
}
|
36
src/mathcommand.js
Normal file
36
src/mathcommand.js
Normal file
@ -0,0 +1,36 @@
|
||||
import Command from '@ckeditor/ckeditor5-core/src/command';
|
||||
import { getSelectedMathModelWidget } from './utils';
|
||||
|
||||
export default class MathCommand extends Command {
|
||||
execute( equation ) {
|
||||
const model = this.editor.model;
|
||||
const selection = model.document.selection;
|
||||
const selectedElement = selection.getSelectedElement();
|
||||
|
||||
model.change( writer => {
|
||||
let mathtex;
|
||||
if ( selectedElement && selectedElement.is( 'mathtex' ) ) {
|
||||
// Update selected element
|
||||
const mode = selectedElement.getAttribute( 'mode' );
|
||||
const display = selectedElement.getAttribute( 'display' );
|
||||
mathtex = writer.createElement( 'mathtex', { equation, mode, display } );
|
||||
} else {
|
||||
// Create new model element
|
||||
mathtex = writer.createElement( 'mathtex', { equation, mode: 'script', display: true } );
|
||||
}
|
||||
model.insertContent( mathtex );
|
||||
writer.setSelection( mathtex, 'on' );
|
||||
} );
|
||||
}
|
||||
|
||||
refresh() {
|
||||
const model = this.editor.model;
|
||||
const selection = model.document.selection;
|
||||
|
||||
const isAllowed = model.schema.checkChild( selection.focus.parent, 'mathtex' );
|
||||
this.isEnabled = isAllowed;
|
||||
|
||||
const selectedEquation = getSelectedMathModelWidget( selection );
|
||||
this.value = selectedEquation ? selectedEquation.getAttribute( 'equation' ) : null;
|
||||
}
|
||||
}
|
160
src/mathediting.js
Normal file
160
src/mathediting.js
Normal file
@ -0,0 +1,160 @@
|
||||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
||||
|
||||
import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
|
||||
import Widget from '@ckeditor/ckeditor5-widget/src/widget';
|
||||
|
||||
import MathCommand from './mathcommand';
|
||||
|
||||
import { renderEquation } from './utils';
|
||||
|
||||
export default class MathEditing extends Plugin {
|
||||
static get requires() {
|
||||
return [ Widget ];
|
||||
}
|
||||
|
||||
static get pluginName() {
|
||||
return 'MathEditing';
|
||||
}
|
||||
|
||||
init() {
|
||||
const editor = this.editor;
|
||||
editor.commands.add( 'math', new MathCommand( editor ) );
|
||||
|
||||
this._defineSchema();
|
||||
this._defineConverters();
|
||||
}
|
||||
|
||||
_defineSchema() {
|
||||
const schema = this.editor.model.schema;
|
||||
|
||||
schema.register( 'mathtex', {
|
||||
allowWhere: '$text',
|
||||
isInline: true,
|
||||
isObject: true,
|
||||
allowAttributes: [ 'equation', 'type', 'display' ]
|
||||
} );
|
||||
}
|
||||
|
||||
_defineConverters() {
|
||||
const conversion = this.editor.conversion;
|
||||
|
||||
|
||||
// View -> Model
|
||||
conversion.for( 'upcast' )
|
||||
// MathJax inline way (e.g. <script type="math/tex">\sqrt{\frac{a}{b}}</script>)
|
||||
.elementToElement( {
|
||||
view: {
|
||||
name: 'script',
|
||||
attributes: {
|
||||
type: 'math/tex'
|
||||
}
|
||||
},
|
||||
model: ( viewElement, modelWriter ) => {
|
||||
const equation = viewElement.getChild( 0 ).data.trim();
|
||||
return modelWriter.createElement( 'mathtex', { equation, type: 'script', display: false } );
|
||||
}
|
||||
} )
|
||||
// MathJax display way (e.g. <script type="math/tex; mode=display">\sqrt{\frac{a}{b}}</script>)
|
||||
.elementToElement( {
|
||||
view: {
|
||||
name: 'script',
|
||||
attributes: {
|
||||
type: 'math/tex; mode=display'
|
||||
}
|
||||
},
|
||||
model: ( viewElement, modelWriter ) => {
|
||||
const equation = viewElement.getChild( 0 ).data.trim();
|
||||
return modelWriter.createElement( 'mathtex', { equation, type: 'script', display: true } );
|
||||
}
|
||||
} )
|
||||
// CKEditor 4 way (e.g. <span class="math-tex">\( \sqrt{\frac{a}{b}} \)</span>)
|
||||
.elementToElement( {
|
||||
view: {
|
||||
name: 'span',
|
||||
classes: [ 'math-tex' ]
|
||||
},
|
||||
model: ( viewElement, modelWriter ) => {
|
||||
let 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();
|
||||
}
|
||||
|
||||
return modelWriter.createElement( 'mathtex', { equation, type: 'span', display: hasDisplayDelimiters } );
|
||||
}
|
||||
} );
|
||||
|
||||
// Model -> View (element)
|
||||
conversion.for( 'editingDowncast' ).elementToElement( {
|
||||
model: 'mathtex',
|
||||
view: ( modelItem, viewWriter ) => {
|
||||
const widgetElement = createMathtexEditingView( modelItem, viewWriter );
|
||||
return toWidget( widgetElement, viewWriter );
|
||||
}
|
||||
} );
|
||||
|
||||
// Model -> Data
|
||||
conversion.for( 'dataDowncast' ).elementToElement( {
|
||||
model: 'mathtex',
|
||||
view: createMathtexView
|
||||
} );
|
||||
|
||||
// Create view for editor
|
||||
function createMathtexEditingView( modelItem, viewWriter ) {
|
||||
const equation = modelItem.getAttribute( 'equation' );
|
||||
const display = modelItem.getAttribute( 'display' );
|
||||
|
||||
|
||||
// CKEngine render multiple times if using span instead of div
|
||||
const mathtexView = viewWriter.createContainerElement( 'div', {
|
||||
style: display ? 'display: block;' : 'display: inline-block;',
|
||||
class: 'mathtex'
|
||||
} );
|
||||
|
||||
// Div is formatted as parent container
|
||||
const uiElement = viewWriter.createUIElement( 'div', null, function( domDocument ) {
|
||||
const domElement = this.toDomElement( domDocument );
|
||||
|
||||
renderEquation( equation, domElement, 'mathjax', display );
|
||||
|
||||
return domElement;
|
||||
} );
|
||||
|
||||
viewWriter.insert( viewWriter.createPositionAt( mathtexView, 0 ), uiElement );
|
||||
|
||||
return mathtexView;
|
||||
}
|
||||
|
||||
// Create view for data
|
||||
function createMathtexView( modelItem, viewWriter ) {
|
||||
const equation = modelItem.getAttribute( 'equation' );
|
||||
const type = modelItem.getAttribute( 'type' );
|
||||
const display = modelItem.getAttribute( 'display' );
|
||||
|
||||
if ( type === 'span' ) {
|
||||
const mathtexView = viewWriter.createContainerElement( 'span', {
|
||||
class: 'math-tex'
|
||||
} );
|
||||
|
||||
if ( display ) {
|
||||
viewWriter.insert( viewWriter.createPositionAt( mathtexView, 0 ), viewWriter.createText( '\\[' + equation + '\\]' ) );
|
||||
} else {
|
||||
viewWriter.insert( viewWriter.createPositionAt( mathtexView, 0 ), viewWriter.createText( '\\(' + equation + '\\)' ) );
|
||||
}
|
||||
|
||||
return mathtexView;
|
||||
} else {
|
||||
const mathtexView = viewWriter.createContainerElement( 'script', {
|
||||
type: display ? 'math/tex; mode=display': 'math/tex'
|
||||
} );
|
||||
|
||||
viewWriter.insert( viewWriter.createPositionAt( mathtexView, 0 ), viewWriter.createText( equation ) );
|
||||
|
||||
return mathtexView;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
208
src/mathui.js
Normal file
208
src/mathui.js
Normal file
@ -0,0 +1,208 @@
|
||||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
||||
import ClickObserver from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver';
|
||||
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
|
||||
import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler';
|
||||
|
||||
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
|
||||
import MainFormView from './ui/mainformview';
|
||||
|
||||
// Need math commands from there
|
||||
import MathEditing from './mathediting';
|
||||
|
||||
import pluginIcon from '../theme/icons/icon.svg';
|
||||
|
||||
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._form = this._createFormView();
|
||||
|
||||
this._balloon = editor.plugins.get( ContextualBalloon );
|
||||
|
||||
this._createToolbarMathButton();
|
||||
|
||||
this._enableUserBalloonInteractions();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
|
||||
this._form.destroy();
|
||||
}
|
||||
|
||||
_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 engine = 'mathjax';
|
||||
|
||||
const formView = new MainFormView( editor.locale, engine );
|
||||
|
||||
formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' );
|
||||
|
||||
// Listen to 'submit' button click
|
||||
this.listenTo( formView, 'submit', () => {
|
||||
editor.execute( 'math', formView.equation );
|
||||
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._form,
|
||||
position: this._getBalloonPositionData(),
|
||||
} );
|
||||
|
||||
if ( this._balloon.visibleView === this._form ) {
|
||||
this._form.mathInputView.select();
|
||||
}
|
||||
|
||||
this._form.equation = mathCommand.value || '';
|
||||
}
|
||||
|
||||
_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._form.saveButtonView.focus();
|
||||
|
||||
this._balloon.remove( this._form );
|
||||
|
||||
this.editor.editing.view.focus();
|
||||
}
|
||||
}
|
||||
|
||||
_getBalloonPositionData() {
|
||||
const view = this.editor.editing.view;
|
||||
const viewDocument = view.document;
|
||||
const target = view.domConverter.viewRangeToDom( viewDocument.selection.getFirstRange() );
|
||||
return { target };
|
||||
}
|
||||
|
||||
_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 = pluginIcon;
|
||||
button.keystroke = mathKeystroke;
|
||||
button.tooltip = true;
|
||||
button.isToggleable = true;
|
||||
|
||||
button.bind( 'isEnabled' ).to( mathCommand, 'isEnabled' );
|
||||
|
||||
this.listenTo( button, 'execute', () => this._showUI() );
|
||||
|
||||
return button;
|
||||
} );
|
||||
}
|
||||
|
||||
_enableUserBalloonInteractions() {
|
||||
// Close the panel on the Esc key press when the editable has focus and the balloon is visible.
|
||||
this.editor.keystrokes.set( 'Esc', ( data, cancel ) => {
|
||||
if ( this._isUIVisible ) {
|
||||
this._hideUI();
|
||||
cancel();
|
||||
}
|
||||
} );
|
||||
|
||||
// Close on click outside of balloon panel element.
|
||||
clickOutsideHandler( {
|
||||
emitter: this._form,
|
||||
activator: () => this._isFormInPanel,
|
||||
contextElements: [ this._balloon.view.element ],
|
||||
callback: () => this._hideUI()
|
||||
} );
|
||||
}
|
||||
|
||||
get _isUIVisible() {
|
||||
const visibleView = this._balloon.visibleView;
|
||||
|
||||
return visibleView == this._form;
|
||||
}
|
||||
|
||||
get _isFormInPanel() {
|
||||
return this._balloon.hasView( this._form );
|
||||
}
|
||||
}
|
167
src/ui/mainformview.js
Normal file
167
src/ui/mainformview.js
Normal file
@ -0,0 +1,167 @@
|
||||
import View from '@ckeditor/ckeditor5-ui/src/view';
|
||||
import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection';
|
||||
|
||||
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
|
||||
import LabeledInputView from '@ckeditor/ckeditor5-ui/src/labeledinput/labeledinputview';
|
||||
import InputTextView from '@ckeditor/ckeditor5-ui/src/inputtext/inputtextview';
|
||||
import LabelView from '@ckeditor/ckeditor5-ui/src/label/labelview';
|
||||
|
||||
import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
|
||||
import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
|
||||
import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler';
|
||||
|
||||
import checkIcon from '@ckeditor/ckeditor5-core/theme/icons/check.svg';
|
||||
import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg';
|
||||
|
||||
import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler';
|
||||
|
||||
import MathView from './mathview';
|
||||
|
||||
import '../../theme/mathform.css';
|
||||
|
||||
export default class MainFormView extends View {
|
||||
constructor( locale, engine ) {
|
||||
super( locale );
|
||||
|
||||
const t = locale.t;
|
||||
|
||||
// Create key event & focus trackers
|
||||
this._createKeyAndFocusTrackers();
|
||||
|
||||
// Equation input
|
||||
this.mathInputView = this._createMathInput();
|
||||
|
||||
// Preview label
|
||||
this.previewLabel = new LabelView( locale );
|
||||
this.previewLabel.text = t( 'Equation preview' );
|
||||
|
||||
// Math element
|
||||
this.mathView = new MathView( engine, locale );
|
||||
|
||||
// Submit button
|
||||
this.saveButtonView = this._createButton( t( 'Save' ), checkIcon, 'ck-button-save', null );
|
||||
this.saveButtonView.type = 'submit';
|
||||
|
||||
// Cancel button
|
||||
this.cancelButtonView = this._createButton( t( 'Cancel' ), cancelIcon, 'ck-button-cancel', 'cancel' );
|
||||
|
||||
|
||||
// Add UI elements to template
|
||||
this.setTemplate( {
|
||||
tag: 'form',
|
||||
attributes: {
|
||||
class: [
|
||||
'ck',
|
||||
'ck-math-form',
|
||||
],
|
||||
tabindex: '-1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
tag: 'div',
|
||||
attributes: {
|
||||
class: [
|
||||
'ck-math-view'
|
||||
]
|
||||
},
|
||||
children: [
|
||||
this.mathInputView,
|
||||
this.previewLabel,
|
||||
this.mathView
|
||||
]
|
||||
},
|
||||
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.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.inputView.element.value;
|
||||
}
|
||||
|
||||
set equation( equation ) {
|
||||
this.mathInputView.inputView.element.value = equation;
|
||||
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 LabeledInputView( this.locale, InputTextView );
|
||||
const inputView = mathInput.inputView;
|
||||
mathInput.infoText = t( 'Insert equation in TeX format.' );
|
||||
inputView.on( 'input', () => {
|
||||
this.mathView.value = inputView.element.value;
|
||||
} );
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
35
src/ui/mathview.js
Normal file
35
src/ui/mathview.js
Normal file
@ -0,0 +1,35 @@
|
||||
import View from '@ckeditor/ckeditor5-ui/src/view';
|
||||
import { renderEquation } from '../utils';
|
||||
|
||||
export default class MathView extends View {
|
||||
constructor( engine, locale ) {
|
||||
super( locale );
|
||||
|
||||
this.engine = engine;
|
||||
|
||||
this.set( 'value', '' );
|
||||
|
||||
this.on( 'change:value', () => {
|
||||
this.updateMath();
|
||||
} );
|
||||
|
||||
this.setTemplate( {
|
||||
tag: 'div',
|
||||
attributes: {
|
||||
class: [
|
||||
'ck',
|
||||
'ck-math-preview'
|
||||
],
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
updateMath() {
|
||||
renderEquation( this.value, this.element, this.engine );
|
||||
}
|
||||
|
||||
render() {
|
||||
super.render();
|
||||
this.updateMath();
|
||||
}
|
||||
}
|
33
src/utils.js
Normal file
33
src/utils.js
Normal file
@ -0,0 +1,33 @@
|
||||
export function renderEquation( equation, element, engine = 'katex', display = false ) {
|
||||
if ( engine === 'mathjax' && typeof katex !== 'mathjax' ) {
|
||||
if (display) {
|
||||
element.innerHTML = '\\[' + equation + '\\]';
|
||||
} else {
|
||||
element.innerHTML = '\\(' + equation + '\\)';
|
||||
}
|
||||
/* eslint-disable */
|
||||
MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, element ] );
|
||||
/* eslint-enable */
|
||||
}
|
||||
else if ( engine === 'katex' && typeof katex !== 'undefined' ) {
|
||||
/* eslint-disable */
|
||||
katex.render( equation, element, {
|
||||
throwOnError: false,
|
||||
displayMode: display
|
||||
} );
|
||||
/* eslint-enable */
|
||||
} else {
|
||||
element.innerHTML = equation;
|
||||
console.warn( 'math-tex-typesetting-missing: Missing the mathematical typesetting engine for tex.' );
|
||||
}
|
||||
}
|
||||
|
||||
export function getSelectedMathModelWidget( selection ) {
|
||||
const selectedElement = selection.getSelectedElement();
|
||||
|
||||
if ( selectedElement && selectedElement.is( 'mathtex' ) ) {
|
||||
return selectedElement;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
1
theme/icons/icon.svg
Normal file
1
theme/icons/icon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.44 10.78" height="40.74" width="58.35"><path d="M8.15 0c-.06 0-.1.02-.11.03a.12.12 0 0 0-.02.01 6.81 6.81 0 0 0-2.32 4.9v.9a6.82 6.82 0 0 0 2.32 4.9.12.12 0 0 0 .02 0c.02.02.06.04.11.04.07 0 .12-.03.16-.07a.22.22 0 0 0 0-.32.12.12 0 0 0-.02-.02A4.4 4.4 0 0 1 7 8.44a7.62 7.62 0 0 1-.5-2.6v-.9c0-.82.19-1.76.5-2.6A4.4 4.4 0 0 1 8.3.42.12.12 0 0 0 8.3.39a.22.22 0 0 0 .08-.16.22.22 0 0 0-.07-.16.22.22 0 0 0-.16-.07zm4.83 0a.22.22 0 0 0-.16.07.22.22 0 0 0-.07.16c0 .08.05.13.08.16a.12.12 0 0 0 .01.02c.52.39.98 1.1 1.3 1.94.3.83.49 1.77.49 2.6v.88c0 .83-.18 1.78-.5 2.6a4.4 4.4 0 0 1-1.29 1.95.22.22 0 0 0-.01.33c.03.04.08.07.15.07.05 0 .09-.02.12-.03a.12.12 0 0 0 .02-.01 6.82 6.82 0 0 0 2.32-4.9v-.9a6.81 6.81 0 0 0-2.32-4.9.12.12 0 0 0-.02 0c-.03-.02-.06-.04-.12-.04zm-8.5.46c-.4 0-1.13.23-1.46 1.32-.06.2-.11.45-.33 1.58h-.64c-.1 0-.19-.01-.28.03a.25.25 0 0 0-.12.12.38.38 0 0 0-.03.17c0 .04 0 .1.04.14.03.04.07.07.11.08.09.03.16.02.26.02h.56l-.77 4.04c-.1.51-.19 1-.32 1.36-.06.18-.14.32-.22.4-.08.1-.16.13-.26.13-.03 0-.1 0-.2-.03.11-.05.2-.13.26-.2a.7.7 0 0 0 .13-.4.48.48 0 0 0-.16-.38.53.53 0 0 0-.35-.12c-.34 0-.7.3-.7.76 0 .27.14.5.34.64s.44.2.68.2c.33 0 .61-.17.83-.4.21-.21.37-.48.47-.69.18-.35.32-.84.43-1.25a14.17 14.17 0 0 0 .18-.8l.61-3.26h.81c.1 0 .2.01.3-.03.04-.03.09-.07.11-.13.02-.05.03-.1.03-.17 0-.05-.01-.1-.05-.14a.23.23 0 0 0-.11-.07c-.08-.03-.16-.02-.25-.02h-.73l.2-1.07a26.3 26.3 0 0 1 .24-1.07c.08-.17.22-.3.39-.3l.21.05a.7.7 0 0 0-.25.2.7.7 0 0 0-.13.4c0 .15.06.28.16.37.1.08.22.12.35.12.34 0 .7-.3.7-.76 0-.28-.15-.5-.35-.64-.2-.14-.45-.2-.7-.2zm5.4 2.78c-.6 0-1.06.37-1.36.76-.16.2-.27.4-.35.57-.07.18-.12.3-.12.42 0 .1.08.18.14.2.06.03.1.02.1.02.06 0 .12 0 .18-.04.05-.05.07-.1.08-.17v.02c.35-1.09 1-1.3 1.3-1.3.09 0 .2.01.29.09.09.07.17.2.17.5 0 .27-.18 1-.57 2.48a1.8 1.8 0 0 1-.37.75.7.7 0 0 1-.52.26c-.04 0-.13 0-.22-.03a.68.68 0 0 0 .3-.56.47.47 0 0 0-.18-.39.55.55 0 0 0-.32-.1c-.4 0-.7.33-.7.74 0 .28.16.5.38.63.21.13.48.18.73.18.39 0 .69-.2.89-.41.09-.1.15-.19.2-.27.2.36.59.68 1.16.68.6 0 1.05-.37 1.35-.76.15-.2.27-.4.34-.57.08-.18.12-.3.12-.42a.24.24 0 0 0-.11-.2c-.06-.03-.12-.02-.13-.02a.26.26 0 0 0-.18.06c-.05.05-.06.1-.07.14-.34 1.1-1.02 1.3-1.3 1.3-.17 0-.27-.06-.35-.17a.72.72 0 0 1-.11-.4c0-.22.06-.45.18-.91l.36-1.45c.03-.14.1-.44.25-.7.15-.25.36-.46.68-.46.03 0 .12 0 .22.03a.7.7 0 0 0-.32.56c0 .11.04.23.13.33.08.1.22.16.4.16.14 0 .3-.06.44-.18a.73.73 0 0 0 .24-.55c0-.32-.2-.54-.42-.66a1.52 1.52 0 0 0-.68-.16c-.34 0-.62.16-.82.34a1.8 1.8 0 0 0-.3.35 1.32 1.32 0 0 0-.5-.54 1.37 1.37 0 0 0-.63-.15z" style="line-height:1.25;-inkscape-font-specification:'Latin Modern Math'" font-weight="400" font-size="10.58" font-family="Latin Modern Math" letter-spacing="-1.06" word-spacing="0"/></svg>
|
After Width: | Height: | Size: 2.7 KiB |
35
theme/mathform.css
Normal file
35
theme/mathform.css
Normal file
@ -0,0 +1,35 @@
|
||||
@import "@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css";
|
||||
|
||||
.ck.ck-math-form {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
@mixin ck-media-phone {
|
||||
flex-wrap: wrap;
|
||||
|
||||
& .ck-math-view {
|
||||
flex-basis: 100%;
|
||||
|
||||
& .ck-labeled-input {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
& .ck-label {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
& .ck-button {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* FIXME: mathjax isn't working with .ck.ck-reset_all * without this fix*/
|
||||
.ck.ck-math-preview * {
|
||||
vertical-align: initial;
|
||||
text-align: center;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user