mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	Implement plugin
This commit is contained in:
		
							
								
								
									
										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; | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user