mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 03:16:11 +01:00
chore(ckeditor5-math): integrate source code
This commit is contained in:
@@ -1,7 +1,35 @@
|
||||
import type { Math } from './index.js';
|
||||
import type Math from './math.js';
|
||||
import MathCommand from './mathcommand.js';
|
||||
import MathEditing from './mathediting.js';
|
||||
import MathUI from './mathui.js';
|
||||
import { KatexOptions } from './typings-external.js';
|
||||
|
||||
declare module 'ckeditor5' {
|
||||
interface PluginsMap {
|
||||
[ Math.pluginName ]: Math;
|
||||
[ MathEditing.pluginName ]: MathEditing;
|
||||
[ MathUI.pluginName ]: MathUI;
|
||||
}
|
||||
|
||||
interface CommandsMap {
|
||||
math: MathCommand;
|
||||
}
|
||||
|
||||
interface EditorConfig {
|
||||
math?: {
|
||||
engine?:
|
||||
| 'mathjax'
|
||||
| 'katex'
|
||||
| ( ( equation: string, element: HTMLElement, display: boolean ) => void )
|
||||
| undefined;
|
||||
lazyLoad?: undefined | ( () => Promise<void> );
|
||||
outputType?: 'script' | 'span' | undefined;
|
||||
className?: string | undefined;
|
||||
forceOutputType?: boolean | undefined;
|
||||
enablePreview?: boolean | undefined;
|
||||
previewClassName?: Array<string> | undefined;
|
||||
popupClassName?: Array<string> | undefined;
|
||||
katexRenderOptions?: Partial<KatexOptions> | undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
57
packages/ckeditor5-math/src/autoformatmath.ts
Normal file
57
packages/ckeditor5-math/src/autoformatmath.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Plugin, logWarning, blockAutoformatEditing } from 'ckeditor5';
|
||||
// eslint-disable-next-line ckeditor5-rules/allow-imports-only-from-main-package-entry-point
|
||||
import Math from './math.js';
|
||||
import MathCommand from './mathcommand.js';
|
||||
import MathUI from './mathui.js';
|
||||
|
||||
export default class AutoformatMath extends Plugin {
|
||||
public static get requires() {
|
||||
return [ Math, 'Autoformat' ] as const;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public init(): void {
|
||||
const editor = this.editor;
|
||||
|
||||
if ( !editor.plugins.has( 'Math' ) ) {
|
||||
logWarning( 'autoformat-math-feature-missing', editor );
|
||||
}
|
||||
}
|
||||
|
||||
public afterInit(): void {
|
||||
const editor = this.editor;
|
||||
const command = editor.commands.get( 'math' );
|
||||
|
||||
if ( command instanceof MathCommand ) {
|
||||
const callback = () => {
|
||||
if ( !command.isEnabled ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
command.display = true;
|
||||
|
||||
// Wait until selection is removed.
|
||||
window.setTimeout(
|
||||
() => {
|
||||
const mathUIInstance = editor.plugins.get( 'MathUI' );
|
||||
if ( mathUIInstance instanceof MathUI ) {
|
||||
mathUIInstance._showUI();
|
||||
}
|
||||
},
|
||||
50
|
||||
);
|
||||
};
|
||||
|
||||
// @ts-expect-error: blockAutoformatEditing expects an Autoformat instance even though it works with any Plugin instance
|
||||
blockAutoformatEditing( editor, this, /^\$\$$/, callback );
|
||||
// @ts-expect-error: blockAutoformatEditing expects an Autoformat instance even though it works with any Plugin instance
|
||||
blockAutoformatEditing( editor, this, /^\\\[$/, callback );
|
||||
}
|
||||
}
|
||||
|
||||
public static get pluginName() {
|
||||
return 'AutoformatMath' as const;
|
||||
}
|
||||
}
|
||||
133
packages/ckeditor5-math/src/automath.ts
Normal file
133
packages/ckeditor5-math/src/automath.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { Clipboard, Plugin, type Editor, LivePosition, LiveRange, Undo } from 'ckeditor5';
|
||||
import { extractDelimiters, hasDelimiters, delimitersCounts } from './utils.js';
|
||||
|
||||
export default class AutoMath extends Plugin {
|
||||
public static get requires() {
|
||||
return [ Clipboard, Undo ] as const;
|
||||
}
|
||||
|
||||
public static get pluginName() {
|
||||
return 'AutoMath' as const;
|
||||
}
|
||||
|
||||
private _timeoutId: null | number;
|
||||
private _positionToInsert: null | LivePosition;
|
||||
|
||||
constructor( editor: Editor ) {
|
||||
super( editor );
|
||||
|
||||
this._timeoutId = null;
|
||||
|
||||
this._positionToInsert = null;
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
const editor = this.editor;
|
||||
const modelDocument = editor.model.document;
|
||||
|
||||
this.listenTo( editor.plugins.get( Clipboard ), 'inputTransformation', () => {
|
||||
const firstRange = modelDocument.selection.getFirstRange();
|
||||
if ( !firstRange ) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 ) {
|
||||
window.clearTimeout( this._timeoutId );
|
||||
this._positionToInsert?.detach();
|
||||
|
||||
this._timeoutId = null;
|
||||
this._positionToInsert = null;
|
||||
}
|
||||
}, { priority: 'high' } );
|
||||
}
|
||||
|
||||
private _mathBetweenPositions(
|
||||
leftPosition: LivePosition,
|
||||
rightPosition: LivePosition
|
||||
) {
|
||||
const editor = this.editor;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const mathConfig = this.editor.config.get( 'math' );
|
||||
|
||||
const equationRange = new LiveRange( leftPosition, rightPosition );
|
||||
const walker = equationRange.getWalker( { ignoreElementEnd: true } );
|
||||
|
||||
let text = '';
|
||||
|
||||
// Get equation text
|
||||
for ( const node of walker ) {
|
||||
if ( node.item.is( '$textProxy' ) ) {
|
||||
text += node.item.data;
|
||||
}
|
||||
}
|
||||
|
||||
text = text.trim();
|
||||
|
||||
// Skip if don't have delimiters
|
||||
if ( !hasDelimiters( text ) || delimitersCounts( text ) !== 2 ) {
|
||||
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 = window.setTimeout( () => {
|
||||
editor.model.change( writer => {
|
||||
this._timeoutId = null;
|
||||
|
||||
writer.remove( equationRange );
|
||||
|
||||
let insertPosition: LivePosition | null;
|
||||
|
||||
// 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( innerWriter => {
|
||||
const params = Object.assign( extractDelimiters( text ), {
|
||||
type: mathConfig?.outputType
|
||||
} );
|
||||
const mathElement = innerWriter.createElement( params.display ? 'mathtex-display' : 'mathtex-inline', params
|
||||
);
|
||||
|
||||
editor.model.insertContent( mathElement, insertPosition );
|
||||
|
||||
innerWriter.setSelection( mathElement, 'on' );
|
||||
} );
|
||||
|
||||
this._positionToInsert?.detach();
|
||||
this._positionToInsert = null;
|
||||
} );
|
||||
}, 100 );
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import ckeditor from './../theme/icons/ckeditor.svg';
|
||||
import './augmentation.js';
|
||||
import "../theme/mathform.css";
|
||||
|
||||
export { default as Math } from './math.js';
|
||||
export { default as AutoformatMath } from './autoformatmath.js';
|
||||
|
||||
export const icons = {
|
||||
ckeditor
|
||||
|
||||
@@ -1,39 +1,14 @@
|
||||
import { Plugin, ButtonView } from 'ckeditor5';
|
||||
|
||||
import ckeditor5Icon from '../theme/icons/ckeditor.svg';
|
||||
import { Plugin, Widget } from 'ckeditor5';
|
||||
import MathEditing from './mathediting.js';
|
||||
import MathUI from './mathui.js';
|
||||
import AutoMath from './automath.js';
|
||||
|
||||
export default class Math extends Plugin {
|
||||
public static get requires() {
|
||||
return [ MathEditing, MathUI, AutoMath, Widget ] as const;
|
||||
}
|
||||
|
||||
public static get pluginName() {
|
||||
return 'Math' as const;
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
const editor = this.editor;
|
||||
const t = editor.t;
|
||||
const model = editor.model;
|
||||
|
||||
// Register the "math" button, so it can be displayed in the toolbar.
|
||||
editor.ui.componentFactory.add( 'math', locale => {
|
||||
const view = new ButtonView( locale );
|
||||
|
||||
view.set( {
|
||||
label: t( 'Math' ),
|
||||
icon: ckeditor5Icon,
|
||||
tooltip: true
|
||||
} );
|
||||
|
||||
// Insert a text into the editor after clicking the button.
|
||||
this.listenTo( view, 'execute', () => {
|
||||
model.change( writer => {
|
||||
const textNode = writer.createText( 'Hello CKEditor 5!' );
|
||||
|
||||
model.insertContent( textNode );
|
||||
} );
|
||||
|
||||
editor.editing.view.focus();
|
||||
} );
|
||||
|
||||
return view;
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
64
packages/ckeditor5-math/src/mathcommand.ts
Normal file
64
packages/ckeditor5-math/src/mathcommand.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Command } from 'ckeditor5';
|
||||
import { getSelectedMathModelWidget } from './utils.js';
|
||||
|
||||
export default class MathCommand extends Command {
|
||||
public override value: string | null = null;
|
||||
public override execute(
|
||||
equation: string,
|
||||
display?: boolean,
|
||||
outputType: 'script' | 'span' = 'script',
|
||||
forceOutputType?: boolean
|
||||
): void {
|
||||
const model = this.editor.model;
|
||||
const selection = model.document.selection;
|
||||
const selectedElement = selection.getSelectedElement();
|
||||
|
||||
model.change( writer => {
|
||||
let mathtex;
|
||||
if (
|
||||
selectedElement &&
|
||||
( selectedElement.is( 'element', 'mathtex-inline' ) ||
|
||||
selectedElement.is( 'element', 'mathtex-display' ) )
|
||||
) {
|
||||
// Update selected element
|
||||
const typeAttr = selectedElement.getAttribute( 'type' );
|
||||
|
||||
// Use already set type if found and is not forced
|
||||
const type = forceOutputType ?
|
||||
outputType :
|
||||
typeAttr || outputType;
|
||||
|
||||
mathtex = writer.createElement(
|
||||
display ? 'mathtex-display' : 'mathtex-inline',
|
||||
{ equation, type, display }
|
||||
);
|
||||
} else {
|
||||
// Create new model element
|
||||
mathtex = writer.createElement(
|
||||
display ? 'mathtex-display' : 'mathtex-inline',
|
||||
{ equation, type: outputType, display }
|
||||
);
|
||||
}
|
||||
model.insertContent( mathtex );
|
||||
} );
|
||||
}
|
||||
|
||||
public display = false;
|
||||
|
||||
public override refresh(): void {
|
||||
const model = this.editor.model;
|
||||
const selection = model.document.selection;
|
||||
const selectedElement = selection.getSelectedElement();
|
||||
|
||||
this.isEnabled =
|
||||
selectedElement === null ||
|
||||
selectedElement.is( 'element', 'mathtex-inline' ) ||
|
||||
selectedElement.is( 'element', 'mathtex-display' );
|
||||
|
||||
const selectedEquation = getSelectedMathModelWidget( selection );
|
||||
const value = selectedEquation?.getAttribute( 'equation' );
|
||||
this.value = typeof value === 'string' ? value : null;
|
||||
const display = selectedEquation?.getAttribute( 'display' );
|
||||
this.display = typeof display === 'boolean' ? display : false;
|
||||
}
|
||||
}
|
||||
300
packages/ckeditor5-math/src/mathediting.ts
Normal file
300
packages/ckeditor5-math/src/mathediting.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
import MathCommand from './mathcommand.js';
|
||||
import { type Editor, Plugin, toWidget, Widget, viewToModelPositionOutsideModelElement, type DowncastWriter, type Element, CKEditorError, uid } from 'ckeditor5';
|
||||
import { renderEquation, extractDelimiters } from './utils.js';
|
||||
|
||||
export default class MathEditing extends Plugin {
|
||||
public static get requires() {
|
||||
return [ Widget ] as const;
|
||||
}
|
||||
|
||||
public static get pluginName() {
|
||||
return 'MathEditing' as const;
|
||||
}
|
||||
|
||||
constructor( editor: Editor ) {
|
||||
super( editor );
|
||||
editor.config.define( 'math', {
|
||||
engine: 'mathjax',
|
||||
outputType: 'script',
|
||||
className: 'math-tex',
|
||||
forceOutputType: false,
|
||||
enablePreview: true,
|
||||
previewClassName: [],
|
||||
popupClassName: [],
|
||||
katexRenderOptions: {}
|
||||
} );
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
const editor = this.editor;
|
||||
editor.commands.add( 'math', new MathCommand( editor ) );
|
||||
|
||||
this._defineSchema();
|
||||
this._defineConverters();
|
||||
|
||||
editor.editing.mapper.on(
|
||||
'viewToModelPosition',
|
||||
viewToModelPositionOutsideModelElement(
|
||||
editor.model,
|
||||
viewElement => viewElement.hasClass( 'math' )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _defineSchema() {
|
||||
const schema = this.editor.model.schema;
|
||||
schema.register( 'mathtex-inline', {
|
||||
allowWhere: '$text',
|
||||
isInline: true,
|
||||
isObject: true,
|
||||
allowAttributes: [ 'equation', 'type', 'display' ]
|
||||
} );
|
||||
|
||||
schema.register( 'mathtex-display', {
|
||||
inheritAllFrom: '$blockObject',
|
||||
allowAttributes: [ 'equation', 'type', 'display' ]
|
||||
} );
|
||||
}
|
||||
|
||||
private _defineConverters() {
|
||||
const conversion = this.editor.conversion;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const mathConfig = this.editor.config.get( 'math' )!;
|
||||
|
||||
// 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, { writer } ) => {
|
||||
const child = viewElement.getChild( 0 );
|
||||
if ( child?.is( '$text' ) ) {
|
||||
const equation = child.data.trim();
|
||||
return writer.createElement( 'mathtex-inline', {
|
||||
equation,
|
||||
type: mathConfig.forceOutputType ?
|
||||
mathConfig.outputType :
|
||||
'script',
|
||||
display: false
|
||||
} );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} )
|
||||
// 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, { writer } ) => {
|
||||
const child = viewElement.getChild( 0 );
|
||||
if ( child?.is( '$text' ) ) {
|
||||
const equation = child.data.trim();
|
||||
return writer.createElement( 'mathtex-display', {
|
||||
equation,
|
||||
type: mathConfig.forceOutputType ?
|
||||
mathConfig.outputType :
|
||||
'script',
|
||||
display: true
|
||||
} );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} )
|
||||
// CKEditor 4 way (e.g. <span class="math-tex">\( \sqrt{\frac{a}{b}} \)</span>)
|
||||
.elementToElement( {
|
||||
view: {
|
||||
name: 'span',
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
classes: [ mathConfig.className! ]
|
||||
},
|
||||
model: ( viewElement, { writer } ) => {
|
||||
const child = viewElement.getChild( 0 );
|
||||
if ( child?.is( '$text' ) ) {
|
||||
const equation = child.data.trim();
|
||||
|
||||
const params = Object.assign( extractDelimiters( equation ), {
|
||||
type: mathConfig.forceOutputType ?
|
||||
mathConfig.outputType :
|
||||
'span'
|
||||
} );
|
||||
|
||||
return writer.createElement(
|
||||
params.display ? 'mathtex-display' : 'mathtex-inline',
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
} )
|
||||
// KaTeX from Quill: https://github.com/quilljs/quill/blob/develop/formats/formula.js
|
||||
.elementToElement( {
|
||||
view: {
|
||||
name: 'span',
|
||||
classes: [ 'ql-formula' ]
|
||||
},
|
||||
model: ( viewElement, { writer } ) => {
|
||||
const equation = viewElement.getAttribute( 'data-value' );
|
||||
if ( equation == null ) {
|
||||
/**
|
||||
* Couldn't find equation on current element
|
||||
* @error missing-equation
|
||||
*/
|
||||
throw new CKEditorError( 'missing-equation', { pluginName: 'math' } );
|
||||
}
|
||||
return writer.createElement( 'mathtex-inline', {
|
||||
equation: equation.trim(),
|
||||
type: mathConfig.forceOutputType ?
|
||||
mathConfig.outputType :
|
||||
'script',
|
||||
display: false
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
// Model -> View (element)
|
||||
conversion
|
||||
.for( 'editingDowncast' )
|
||||
.elementToElement( {
|
||||
model: 'mathtex-inline',
|
||||
view: ( modelItem, { writer } ) => {
|
||||
const widgetElement = createMathtexEditingView(
|
||||
modelItem,
|
||||
writer
|
||||
);
|
||||
return toWidget( widgetElement, writer );
|
||||
}
|
||||
} )
|
||||
.elementToElement( {
|
||||
model: 'mathtex-display',
|
||||
view: ( modelItem, { writer } ) => {
|
||||
const widgetElement = createMathtexEditingView(
|
||||
modelItem,
|
||||
writer
|
||||
);
|
||||
return toWidget( widgetElement, writer );
|
||||
}
|
||||
} );
|
||||
|
||||
// Model -> Data
|
||||
conversion
|
||||
.for( 'dataDowncast' )
|
||||
.elementToElement( {
|
||||
model: 'mathtex-inline',
|
||||
view: createMathtexView
|
||||
} )
|
||||
.elementToElement( {
|
||||
model: 'mathtex-display',
|
||||
view: createMathtexView
|
||||
} );
|
||||
|
||||
// Create view for editor
|
||||
function createMathtexEditingView(
|
||||
modelItem: Element,
|
||||
writer: DowncastWriter
|
||||
) {
|
||||
const equation = String( modelItem.getAttribute( 'equation' ) );
|
||||
const display = !!modelItem.getAttribute( 'display' );
|
||||
|
||||
const styles =
|
||||
'user-select: none; ' +
|
||||
( display ? '' : 'display: inline-block;' );
|
||||
const classes =
|
||||
'ck-math-tex ' +
|
||||
( display ? 'ck-math-tex-display' : 'ck-math-tex-inline' );
|
||||
|
||||
const mathtexView = writer.createContainerElement(
|
||||
display ? 'div' : 'span',
|
||||
{
|
||||
style: styles,
|
||||
class: classes
|
||||
}
|
||||
);
|
||||
|
||||
const uiElement = writer.createUIElement(
|
||||
'div',
|
||||
null,
|
||||
function( domDocument ) {
|
||||
const domElement = this.toDomElement( domDocument );
|
||||
|
||||
void renderEquation(
|
||||
equation,
|
||||
domElement,
|
||||
mathConfig.engine,
|
||||
mathConfig.lazyLoad,
|
||||
display,
|
||||
false,
|
||||
`math-editing-${ uid() }`,
|
||||
mathConfig.previewClassName,
|
||||
mathConfig.katexRenderOptions
|
||||
);
|
||||
|
||||
return domElement;
|
||||
}
|
||||
);
|
||||
|
||||
writer.insert( writer.createPositionAt( mathtexView, 0 ), uiElement );
|
||||
|
||||
return mathtexView;
|
||||
}
|
||||
|
||||
// Create view for data
|
||||
function createMathtexView(
|
||||
modelItem: Element,
|
||||
{ writer }: { writer: DowncastWriter }
|
||||
) {
|
||||
const equation = modelItem.getAttribute( 'equation' );
|
||||
if ( typeof equation != 'string' ) {
|
||||
/**
|
||||
* Couldn't find equation on current element
|
||||
* @error missing-equation
|
||||
*/
|
||||
throw new CKEditorError( 'missing-equation', { pluginName: 'math' } );
|
||||
}
|
||||
|
||||
const type = modelItem.getAttribute( 'type' );
|
||||
const display = modelItem.getAttribute( 'display' );
|
||||
|
||||
if ( type === 'span' ) {
|
||||
const mathtexView = writer.createContainerElement( 'span', {
|
||||
class: mathConfig.className
|
||||
} );
|
||||
|
||||
if ( display ) {
|
||||
writer.insert(
|
||||
writer.createPositionAt( mathtexView, 0 ),
|
||||
writer.createText( '\\[' + equation + '\\]' )
|
||||
);
|
||||
} else {
|
||||
writer.insert(
|
||||
writer.createPositionAt( mathtexView, 0 ),
|
||||
writer.createText( '\\(' + equation + '\\)' )
|
||||
);
|
||||
}
|
||||
|
||||
return mathtexView;
|
||||
} else {
|
||||
const mathtexView = writer.createContainerElement( 'script', {
|
||||
type: display ? 'math/tex; mode=display' : 'math/tex'
|
||||
} );
|
||||
|
||||
writer.insert(
|
||||
writer.createPositionAt( mathtexView, 0 ),
|
||||
writer.createText( equation )
|
||||
);
|
||||
|
||||
return mathtexView;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
281
packages/ckeditor5-math/src/mathui.ts
Normal file
281
packages/ckeditor5-math/src/mathui.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
import MathEditing from './mathediting.js';
|
||||
import MainFormView from './ui/mainformview.js';
|
||||
import mathIcon from '../theme/icons/math.svg';
|
||||
import { Plugin, ClickObserver, ButtonView, ContextualBalloon, clickOutsideHandler, CKEditorError, uid } from 'ckeditor5';
|
||||
import { getBalloonPositionData } from './utils.js';
|
||||
import MathCommand from './mathcommand.js';
|
||||
|
||||
const mathKeystroke = 'Ctrl+M';
|
||||
|
||||
export default class MathUI extends Plugin {
|
||||
public static get requires() {
|
||||
return [ ContextualBalloon, MathEditing ] as const;
|
||||
}
|
||||
|
||||
public static get pluginName() {
|
||||
return 'MathUI' as const;
|
||||
}
|
||||
|
||||
private _previewUid = `math-preview-${ uid() }`;
|
||||
private _balloon: ContextualBalloon = this.editor.plugins.get( ContextualBalloon );
|
||||
public formView: MainFormView | null = null;
|
||||
|
||||
public init(): void {
|
||||
const editor = this.editor;
|
||||
editor.editing.view.addObserver( ClickObserver );
|
||||
|
||||
this._createToolbarMathButton();
|
||||
|
||||
this.formView = this._createFormView();
|
||||
|
||||
this._enableUserBalloonInteractions();
|
||||
}
|
||||
|
||||
public override destroy(): void {
|
||||
super.destroy();
|
||||
|
||||
this.formView?.destroy();
|
||||
|
||||
// Destroy preview element
|
||||
const previewEl = document.getElementById( this._previewUid );
|
||||
if ( previewEl ) {
|
||||
previewEl.parentNode?.removeChild( previewEl );
|
||||
}
|
||||
}
|
||||
|
||||
public _showUI(): void {
|
||||
const editor = this.editor;
|
||||
const mathCommand = editor.commands.get( 'math' );
|
||||
|
||||
if ( !mathCommand?.isEnabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._addFormView();
|
||||
|
||||
this._balloon.showStack( 'main' );
|
||||
}
|
||||
|
||||
private _createFormView() {
|
||||
const editor = this.editor;
|
||||
const mathCommand = editor.commands.get( 'math' );
|
||||
if ( !( mathCommand instanceof MathCommand ) ) {
|
||||
/**
|
||||
* Missing Math command
|
||||
* @error math-command
|
||||
*/
|
||||
throw new CKEditorError( 'math-command' );
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const mathConfig = editor.config.get( 'math' )!;
|
||||
|
||||
const formView = new MainFormView(
|
||||
editor.locale,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
mathConfig.engine!,
|
||||
mathConfig.lazyLoad,
|
||||
mathConfig.enablePreview,
|
||||
this._previewUid,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
mathConfig.previewClassName!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
mathConfig.popupClassName!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
mathConfig.katexRenderOptions!
|
||||
);
|
||||
|
||||
formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' );
|
||||
formView.displayButtonView.bind( 'isOn' ).to( mathCommand, 'display' );
|
||||
|
||||
// Form elements should be read-only when corresponding commands are disabled.
|
||||
formView.mathInputView.bind( 'isReadOnly' ).to( mathCommand, 'isEnabled', value => !value );
|
||||
formView.saveButtonView.bind( 'isEnabled' ).to( mathCommand );
|
||||
formView.displayButtonView.bind( 'isEnabled' ).to( mathCommand );
|
||||
|
||||
// Listen to submit button click
|
||||
this.listenTo( formView, 'submit', () => {
|
||||
editor.execute( 'math', formView.equation, formView.displayButtonView.isOn, mathConfig.outputType, mathConfig.forceOutputType );
|
||||
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;
|
||||
}
|
||||
|
||||
private _addFormView() {
|
||||
if ( this._isFormInPanel ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = this.editor;
|
||||
const mathCommand = editor.commands.get( 'math' );
|
||||
if ( !( mathCommand instanceof MathCommand ) ) {
|
||||
/**
|
||||
* Math command not found
|
||||
* @error plugin-load
|
||||
*/
|
||||
throw new CKEditorError( 'plugin-load', { pluginName: 'math' } );
|
||||
}
|
||||
|
||||
if ( this.formView == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._balloon.add( {
|
||||
view: this.formView,
|
||||
position: getBalloonPositionData( editor )
|
||||
} );
|
||||
|
||||
if ( this._balloon.visibleView === this.formView ) {
|
||||
this.formView.mathInputView.fieldView.element?.select();
|
||||
}
|
||||
|
||||
// Show preview element
|
||||
const previewEl = document.getElementById( this._previewUid );
|
||||
if ( previewEl && this.formView.previewEnabled ) {
|
||||
// Force refresh preview
|
||||
this.formView.mathView?.updateMath();
|
||||
}
|
||||
|
||||
this.formView.equation = mathCommand.value ?? '';
|
||||
this.formView.displayButtonView.isOn = mathCommand.display || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public _hideUI(): void {
|
||||
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();
|
||||
}
|
||||
|
||||
private _closeFormView() {
|
||||
const mathCommand = this.editor.commands.get( 'math' );
|
||||
if ( mathCommand?.value != null ) {
|
||||
this._removeFormView();
|
||||
} else {
|
||||
this._hideUI();
|
||||
}
|
||||
}
|
||||
|
||||
private _removeFormView() {
|
||||
if ( this._isFormInPanel && this.formView ) {
|
||||
this.formView.saveButtonView.focus();
|
||||
|
||||
this._balloon.remove( this.formView );
|
||||
|
||||
// Hide preview element
|
||||
const previewEl = document.getElementById( this._previewUid );
|
||||
if ( previewEl ) {
|
||||
previewEl.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
this.editor.editing.view.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _createToolbarMathButton() {
|
||||
const editor = this.editor;
|
||||
const mathCommand = editor.commands.get( 'math' );
|
||||
if ( !mathCommand ) {
|
||||
/**
|
||||
* Math command not found
|
||||
* @error plugin-load
|
||||
*/
|
||||
throw new CKEditorError( 'plugin-load', { pluginName: '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 = mathIcon;
|
||||
button.keystroke = mathKeystroke;
|
||||
button.tooltip = true;
|
||||
button.isToggleable = true;
|
||||
|
||||
button.bind( 'isEnabled' ).to( mathCommand, 'isEnabled' );
|
||||
|
||||
this.listenTo( button, 'execute', () => {
|
||||
this._showUI();
|
||||
} );
|
||||
|
||||
return button;
|
||||
} );
|
||||
}
|
||||
|
||||
private _enableUserBalloonInteractions() {
|
||||
const editor = this.editor;
|
||||
const viewDocument = this.editor.editing.view.document;
|
||||
this.listenTo( viewDocument, 'click', () => {
|
||||
const mathCommand = editor.commands.get( 'math' );
|
||||
if ( mathCommand?.isEnabled && mathCommand.value ) {
|
||||
this._showUI();
|
||||
}
|
||||
} );
|
||||
|
||||
// Close the panel on the Esc key press when the editable has focus and the balloon is visible.
|
||||
editor.keystrokes.set( 'Esc', ( _data, cancel ) => {
|
||||
if ( this._isUIVisible ) {
|
||||
this._hideUI();
|
||||
cancel();
|
||||
}
|
||||
} );
|
||||
|
||||
// Close on click outside of balloon panel element.
|
||||
if ( this.formView ) {
|
||||
clickOutsideHandler( {
|
||||
emitter: this.formView,
|
||||
activator: () => !!this._isFormInPanel,
|
||||
contextElements: this._balloon.view.element ? [ this._balloon.view.element ] : [],
|
||||
callback: () => { this._hideUI(); }
|
||||
} );
|
||||
} else {
|
||||
throw new Error( 'missing form view' );
|
||||
}
|
||||
}
|
||||
|
||||
private get _isUIVisible() {
|
||||
const visibleView = this._balloon.visibleView;
|
||||
|
||||
return visibleView == this.formView;
|
||||
}
|
||||
|
||||
private get _isFormInPanel() {
|
||||
return this.formView && this._balloon.hasView( this.formView );
|
||||
}
|
||||
}
|
||||
156
packages/ckeditor5-math/src/typings-external.ts
Normal file
156
packages/ckeditor5-math/src/typings-external.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Basic typings for third party, external libraries (KaTeX, MathJax).
|
||||
*/
|
||||
export interface MathJax3 {
|
||||
version: string;
|
||||
tex2chtmlPromise?: ( input: string, options: { display: boolean } ) => Promise<HTMLElement>;
|
||||
tex2svgPromise?: ( input: string, options: { display: boolean } ) => Promise<HTMLElement>;
|
||||
}
|
||||
|
||||
export interface MathJax2 {
|
||||
Hub: { Queue: ( callback: [string, MathJax2['Hub'], string | HTMLElement] | ( () => void ) ) => void };
|
||||
}
|
||||
|
||||
export interface Katex {
|
||||
render( equation: string, el: HTMLElement, options: KatexOptions ): void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var CKEDITOR_MATH_LAZY_LOAD: undefined | Promise<void>;
|
||||
// eslint-disable-next-line no-var
|
||||
var MathJax: undefined | MathJax2 | MathJax3;
|
||||
// eslint-disable-next-line no-var
|
||||
var katex: undefined | Katex;
|
||||
}
|
||||
|
||||
export interface KatexOptions {
|
||||
|
||||
/**
|
||||
* If `true`, math will be rendered in display mode
|
||||
* (math in display style and center math on page)
|
||||
*
|
||||
* If `false`, math will be rendered in inline mode
|
||||
* @default false
|
||||
*/
|
||||
displayMode?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Determines the markup language of the output. The valid choices are:
|
||||
* - `html`: Outputs KaTeX in HTML only.
|
||||
* - `mathml`: Outputs KaTeX in MathML only.
|
||||
* - `htmlAndMathml`: Outputs HTML for visual rendering
|
||||
* and includes MathML for accessibility.
|
||||
*
|
||||
* @default 'htmlAndMathml'
|
||||
*/
|
||||
output?: 'html' | 'mathml' | 'htmlAndMathml' | undefined;
|
||||
|
||||
/**
|
||||
* If `true`, display math has \tags rendered on the left
|
||||
* instead of the right, like \usepackage[leqno]{amsmath} in LaTeX.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
leqno?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* If `true`, display math renders flush left with a 2em left margin,
|
||||
* like \documentclass[fleqn] in LaTeX with the amsmath package.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
fleqn?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* If `true`, KaTeX will throw a `ParseError` when
|
||||
* it encounters an unsupported command or invalid LaTex
|
||||
*
|
||||
* If `false`, KaTeX will render unsupported commands as
|
||||
* text, and render invalid LaTeX as its source code with
|
||||
* hover text giving the error, in color given by errorColor
|
||||
* @default true
|
||||
*/
|
||||
throwOnError?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* A Color string given in format `#XXX` or `#XXXXXX`
|
||||
*/
|
||||
errorColor?: string | undefined;
|
||||
|
||||
/**
|
||||
* A collection of custom macros.
|
||||
*
|
||||
* See `src/macros.js` for its usage
|
||||
*/
|
||||
macros?: unknown;
|
||||
|
||||
/**
|
||||
* Specifies a minimum thickness, in ems, for fraction lines,
|
||||
* \sqrt top lines, {array} vertical lines, \hline, \hdashline,
|
||||
* \underline, \overline, and the borders of \fbox, \boxed, and
|
||||
* \fcolorbox.
|
||||
*/
|
||||
minRuleThickness?: number | undefined;
|
||||
|
||||
/**
|
||||
* If `true`, `\color` will work like LaTeX's `\textcolor`
|
||||
* and takes 2 arguments
|
||||
*
|
||||
* If `false`, `\color` will work like LaTeX's `\color`
|
||||
* and takes 1 argument
|
||||
*
|
||||
* In both cases, `\textcolor` works as in LaTeX
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
colorIsTextColor?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* All user-specified sizes will be caped to `maxSize` ems
|
||||
*
|
||||
* If set to Infinity, users can make elements and space
|
||||
* arbitrarily large
|
||||
*
|
||||
* @default Infinity
|
||||
*/
|
||||
maxSize?: number | undefined;
|
||||
|
||||
/**
|
||||
* Limit the number of macro expansions to specified number
|
||||
*
|
||||
* If set to `Infinity`, marco expander will try to fully expand
|
||||
* as in LaTex
|
||||
*
|
||||
* @default 1000
|
||||
*/
|
||||
maxExpand?: number | undefined;
|
||||
|
||||
/**
|
||||
* If `false` or `"ignore"`, allow features that make
|
||||
* writing in LaTex convenient but not supported by LaTex
|
||||
*
|
||||
* If `true` or `"error"`, throw an error for such transgressions
|
||||
*
|
||||
* If `"warn"`, warn about behavior via `console.warn`
|
||||
*
|
||||
* @default "warn"
|
||||
*/
|
||||
strict?: boolean | string | Function | undefined;
|
||||
|
||||
/**
|
||||
* If `false` (do not trust input), prevent any commands that could enable adverse behavior, rendering them instead in errorColor.
|
||||
*
|
||||
* If `true` (trust input), allow all such commands.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
trust?: boolean | ( ( context: object ) => boolean ) | undefined;
|
||||
|
||||
/**
|
||||
* Place KaTeX code in the global group.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
globalGroup?: boolean | undefined;
|
||||
}
|
||||
270
packages/ckeditor5-math/src/ui/mainformview.ts
Normal file
270
packages/ckeditor5-math/src/ui/mainformview.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { icons, ButtonView, createLabeledInputText, FocusCycler, LabelView, LabeledFieldView, submitHandler, SwitchButtonView, View, ViewCollection, type InputTextView, type FocusableView, Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5';
|
||||
import { extractDelimiters, hasDelimiters } from '../utils.js';
|
||||
import MathView from './mathview.js';
|
||||
import '../../theme/mathform.css';
|
||||
import type { KatexOptions } from '../typings-external.js';
|
||||
|
||||
const { check: checkIcon, cancel: cancelIcon } = icons;
|
||||
|
||||
class MathInputView extends LabeledFieldView<InputTextView> {
|
||||
public value: null | string = null;
|
||||
public isReadOnly = false;
|
||||
|
||||
constructor( locale: Locale ) {
|
||||
super( locale, createLabeledInputText );
|
||||
}
|
||||
}
|
||||
|
||||
export default class MainFormView extends View {
|
||||
public saveButtonView: ButtonView;
|
||||
public mathInputView: MathInputView;
|
||||
public displayButtonView: SwitchButtonView;
|
||||
public cancelButtonView: ButtonView;
|
||||
public previewEnabled: boolean;
|
||||
public previewLabel?: LabelView;
|
||||
public mathView?: MathView;
|
||||
public override locale: Locale = new Locale();
|
||||
public lazyLoad: undefined | ( () => Promise<void> );
|
||||
|
||||
constructor(
|
||||
locale: Locale,
|
||||
engine:
|
||||
| 'mathjax'
|
||||
| 'katex'
|
||||
| ( (
|
||||
equation: string,
|
||||
element: HTMLElement,
|
||||
display: boolean,
|
||||
) => void ),
|
||||
lazyLoad: undefined | ( () => Promise<void> ),
|
||||
previewEnabled = false,
|
||||
previewUid: string,
|
||||
previewClassName: Array<string>,
|
||||
popupClassName: Array<string>,
|
||||
katexRenderOptions: KatexOptions
|
||||
) {
|
||||
super( locale );
|
||||
|
||||
const t = locale.t;
|
||||
|
||||
// Submit button
|
||||
this.saveButtonView = this._createButton( t( 'Save' ), checkIcon, 'ck-button-save', null );
|
||||
this.saveButtonView.type = 'submit';
|
||||
|
||||
// Equation input
|
||||
this.mathInputView = this._createMathInput();
|
||||
|
||||
// Display button
|
||||
this.displayButtonView = this._createDisplayButton();
|
||||
|
||||
// Cancel button
|
||||
this.cancelButtonView = this._createButton( t( 'Cancel' ), cancelIcon, 'ck-button-cancel', 'cancel' );
|
||||
|
||||
this.previewEnabled = previewEnabled;
|
||||
|
||||
let children = [];
|
||||
if ( this.previewEnabled ) {
|
||||
// Preview label
|
||||
this.previewLabel = new LabelView( locale );
|
||||
this.previewLabel.text = t( 'Equation preview' );
|
||||
|
||||
// Math element
|
||||
this.mathView = new MathView( engine, lazyLoad, locale, previewUid, previewClassName, katexRenderOptions );
|
||||
this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' );
|
||||
|
||||
children = [
|
||||
this.mathInputView,
|
||||
this.displayButtonView,
|
||||
this.previewLabel,
|
||||
this.mathView
|
||||
];
|
||||
} else {
|
||||
children = [
|
||||
this.mathInputView,
|
||||
this.displayButtonView
|
||||
];
|
||||
}
|
||||
|
||||
// Add UI elements to template
|
||||
this.setTemplate( {
|
||||
tag: 'form',
|
||||
attributes: {
|
||||
class: [
|
||||
'ck',
|
||||
'ck-math-form',
|
||||
...popupClassName
|
||||
],
|
||||
tabindex: '-1',
|
||||
spellcheck: 'false'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
tag: 'div',
|
||||
attributes: {
|
||||
class: [
|
||||
'ck-math-view'
|
||||
]
|
||||
},
|
||||
children
|
||||
},
|
||||
this.saveButtonView,
|
||||
this.cancelButtonView
|
||||
]
|
||||
} );
|
||||
}
|
||||
|
||||
public override render(): void {
|
||||
super.render();
|
||||
|
||||
// Prevent default form submit event & trigger custom 'submit'
|
||||
submitHandler( {
|
||||
view: this
|
||||
} );
|
||||
|
||||
// Register form elements to focusable elements
|
||||
const childViews = [
|
||||
this.mathInputView,
|
||||
this.displayButtonView,
|
||||
this.saveButtonView,
|
||||
this.cancelButtonView
|
||||
];
|
||||
|
||||
childViews.forEach( v => {
|
||||
if ( v.element ) {
|
||||
this._focusables.add( v );
|
||||
this.focusTracker.add( v.element );
|
||||
}
|
||||
} );
|
||||
|
||||
// Listen to keypresses inside form element
|
||||
if ( this.element ) {
|
||||
this.keystrokes.listenTo( this.element );
|
||||
}
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._focusCycler.focusFirst();
|
||||
}
|
||||
|
||||
public get equation(): string {
|
||||
return this.mathInputView.fieldView.element?.value ?? '';
|
||||
}
|
||||
|
||||
public set equation( equation: string ) {
|
||||
if ( this.mathInputView.fieldView.element ) {
|
||||
this.mathInputView.fieldView.element.value = equation;
|
||||
}
|
||||
if ( this.previewEnabled && this.mathView ) {
|
||||
this.mathView.value = equation;
|
||||
}
|
||||
}
|
||||
|
||||
public focusTracker: FocusTracker = new FocusTracker();
|
||||
public keystrokes: KeystrokeHandler = new KeystrokeHandler();
|
||||
private _focusables = new ViewCollection<FocusableView>();
|
||||
private _focusCycler: FocusCycler = new FocusCycler( {
|
||||
focusables: this._focusables,
|
||||
focusTracker: this.focusTracker,
|
||||
keystrokeHandler: this.keystrokes,
|
||||
actions: {
|
||||
focusPrevious: 'shift + tab',
|
||||
focusNext: 'tab'
|
||||
}
|
||||
} );
|
||||
|
||||
private _createMathInput() {
|
||||
const t = this.locale.t;
|
||||
|
||||
// Create equation input
|
||||
const mathInput = new MathInputView( this.locale );
|
||||
const fieldView = mathInput.fieldView;
|
||||
mathInput.infoText = t( 'Insert equation in TeX format.' );
|
||||
|
||||
const onInput = () => {
|
||||
if ( fieldView.element != null ) {
|
||||
let equationInput = fieldView.element.value.trim();
|
||||
|
||||
// If input has delimiters
|
||||
if ( hasDelimiters( equationInput ) ) {
|
||||
// Get equation without delimiters
|
||||
const params = extractDelimiters( equationInput );
|
||||
|
||||
// Remove delimiters from input field
|
||||
fieldView.element.value = params.equation;
|
||||
|
||||
equationInput = params.equation;
|
||||
|
||||
// update display button and preview
|
||||
this.displayButtonView.isOn = params.display;
|
||||
}
|
||||
if ( this.previewEnabled && this.mathView ) {
|
||||
// Update preview view
|
||||
this.mathView.value = equationInput;
|
||||
}
|
||||
|
||||
this.saveButtonView.isEnabled = !!equationInput;
|
||||
}
|
||||
};
|
||||
|
||||
fieldView.on( 'render', onInput );
|
||||
fieldView.on( 'input', onInput );
|
||||
|
||||
return mathInput;
|
||||
}
|
||||
|
||||
private _createButton(
|
||||
label: string,
|
||||
icon: string,
|
||||
className: string,
|
||||
eventName: string | null
|
||||
) {
|
||||
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;
|
||||
}
|
||||
|
||||
private _createDisplayButton() {
|
||||
const t = this.locale.t;
|
||||
|
||||
const switchButton = new SwitchButtonView( this.locale );
|
||||
|
||||
switchButton.set( {
|
||||
label: t( 'Display mode' ),
|
||||
withText: true
|
||||
} );
|
||||
|
||||
switchButton.extendTemplate( {
|
||||
attributes: {
|
||||
class: 'ck-button-display-toggle'
|
||||
}
|
||||
} );
|
||||
|
||||
switchButton.on( 'execute', () => {
|
||||
// Toggle state
|
||||
switchButton.isOn = !switchButton.isOn;
|
||||
|
||||
if ( this.previewEnabled && this.mathView ) {
|
||||
// Update preview view
|
||||
this.mathView.display = switchButton.isOn;
|
||||
}
|
||||
} );
|
||||
|
||||
return switchButton;
|
||||
}
|
||||
}
|
||||
77
packages/ckeditor5-math/src/ui/mathview.ts
Normal file
77
packages/ckeditor5-math/src/ui/mathview.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { View, type Locale } from 'ckeditor5';
|
||||
import type { KatexOptions } from '../typings-external.js';
|
||||
import { renderEquation } from '../utils.js';
|
||||
|
||||
export default class MathView extends View {
|
||||
public declare value: string;
|
||||
public declare display: boolean;
|
||||
public previewUid: string;
|
||||
public previewClassName: Array<string>;
|
||||
public katexRenderOptions: KatexOptions;
|
||||
public engine:
|
||||
| 'mathjax'
|
||||
| 'katex'
|
||||
| ( ( equation: string, element: HTMLElement, display: boolean ) => void );
|
||||
public lazyLoad: undefined | ( () => Promise<void> );
|
||||
|
||||
constructor(
|
||||
engine:
|
||||
| 'mathjax'
|
||||
| 'katex'
|
||||
| ( (
|
||||
equation: string,
|
||||
element: HTMLElement,
|
||||
display: boolean,
|
||||
) => void ),
|
||||
lazyLoad: undefined | ( () => Promise<void> ),
|
||||
locale: Locale,
|
||||
previewUid: string,
|
||||
previewClassName: Array<string>,
|
||||
katexRenderOptions: KatexOptions
|
||||
) {
|
||||
super( locale );
|
||||
|
||||
this.engine = engine;
|
||||
this.lazyLoad = lazyLoad;
|
||||
this.previewUid = previewUid;
|
||||
this.katexRenderOptions = katexRenderOptions;
|
||||
this.previewClassName = previewClassName;
|
||||
|
||||
this.set( 'value', '' );
|
||||
this.set( 'display', false );
|
||||
|
||||
this.on( 'change', () => {
|
||||
if ( this.isRendered ) {
|
||||
this.updateMath();
|
||||
}
|
||||
} );
|
||||
|
||||
this.setTemplate( {
|
||||
tag: 'div',
|
||||
attributes: {
|
||||
class: [ 'ck', 'ck-math-preview' ]
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
public updateMath(): void {
|
||||
if ( this.element ) {
|
||||
void renderEquation(
|
||||
this.value,
|
||||
this.element,
|
||||
this.engine,
|
||||
this.lazyLoad,
|
||||
this.display,
|
||||
true,
|
||||
this.previewUid,
|
||||
this.previewClassName,
|
||||
this.katexRenderOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public override render(): void {
|
||||
super.render();
|
||||
this.updateMath();
|
||||
}
|
||||
}
|
||||
341
packages/ckeditor5-math/src/utils.ts
Normal file
341
packages/ckeditor5-math/src/utils.ts
Normal file
@@ -0,0 +1,341 @@
|
||||
import type { Editor, Element as CKElement, DocumentSelection, PositioningFunction } from 'ckeditor5';
|
||||
import { BalloonPanelView, CKEditorError } from 'ckeditor5';
|
||||
import type { KatexOptions, MathJax2, MathJax3 } from './typings-external.js';
|
||||
|
||||
export function getSelectedMathModelWidget(
|
||||
selection: DocumentSelection
|
||||
): null | CKElement {
|
||||
const selectedElement = selection.getSelectedElement();
|
||||
|
||||
if (
|
||||
selectedElement &&
|
||||
( selectedElement.is( 'element', 'mathtex-inline' ) ||
|
||||
selectedElement.is( 'element', 'mathtex-display' ) )
|
||||
) {
|
||||
return selectedElement;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Simple MathJax 3 version check
|
||||
export function isMathJaxVersion3( MathJax: unknown ): MathJax is MathJax3 {
|
||||
return (
|
||||
MathJax != null && typeof MathJax == 'object' && 'version' in MathJax && typeof MathJax.version == 'string' &&
|
||||
MathJax.version.split( '.' ).length === 3 &&
|
||||
MathJax.version.split( '.' )[ 0 ] === '3'
|
||||
);
|
||||
}
|
||||
|
||||
// Simple MathJax 2 version check
|
||||
export function isMathJaxVersion2( MathJax: unknown ): MathJax is MathJax2 {
|
||||
return (
|
||||
MathJax != null && typeof MathJax == 'object' && 'Hub' in MathJax );
|
||||
}
|
||||
|
||||
// Check if equation has delimiters.
|
||||
export function hasDelimiters( text: string ): RegExpMatchArray | null {
|
||||
return text.match( /^(\\\[.*?\\\]|\\\(.*?\\\))$/ );
|
||||
}
|
||||
|
||||
// Find delimiters count
|
||||
export function delimitersCounts( text: string ): number | undefined {
|
||||
return text.match( /(\\\[|\\\]|\\\(|\\\))/g )?.length;
|
||||
}
|
||||
|
||||
// Extract delimiters and figure display mode for the model
|
||||
export function extractDelimiters( equation: string ): {
|
||||
equation: string;
|
||||
display: boolean;
|
||||
} {
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
export async function renderEquation(
|
||||
equation: string,
|
||||
element: HTMLElement,
|
||||
engine:
|
||||
| 'katex'
|
||||
| 'mathjax'
|
||||
| undefined
|
||||
| ( (
|
||||
equation: string,
|
||||
element: HTMLElement,
|
||||
display: boolean,
|
||||
) => void ) = 'katex',
|
||||
lazyLoad?: () => Promise<void>,
|
||||
display = false,
|
||||
preview = false,
|
||||
previewUid = '',
|
||||
previewClassName: Array<string> = [],
|
||||
katexRenderOptions: KatexOptions = {}
|
||||
): Promise<void> {
|
||||
if ( engine == 'mathjax' ) {
|
||||
if ( isMathJaxVersion3( MathJax ) ) {
|
||||
selectRenderMode(
|
||||
element,
|
||||
preview,
|
||||
previewUid,
|
||||
previewClassName,
|
||||
el => {
|
||||
renderMathJax3( equation, el, display, () => {
|
||||
if ( preview ) {
|
||||
moveAndScaleElement( element, el );
|
||||
el.style.visibility = 'visible';
|
||||
}
|
||||
} );
|
||||
}
|
||||
);
|
||||
} else {
|
||||
selectRenderMode(
|
||||
element,
|
||||
preview,
|
||||
previewUid,
|
||||
previewClassName,
|
||||
el => {
|
||||
// Fixme: MathJax typesetting cause occasionally math processing error without asynchronous call
|
||||
window.setTimeout( () => {
|
||||
renderMathJax2( equation, el, display );
|
||||
|
||||
// Move and scale after rendering
|
||||
if ( preview && isMathJaxVersion2( MathJax ) ) {
|
||||
// eslint-disable-next-line new-cap
|
||||
MathJax.Hub.Queue( () => {
|
||||
moveAndScaleElement( element, el );
|
||||
el.style.visibility = 'visible';
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
} else if ( engine === 'katex' && window.katex !== undefined ) {
|
||||
selectRenderMode(
|
||||
element,
|
||||
preview,
|
||||
previewUid,
|
||||
previewClassName,
|
||||
el => {
|
||||
if ( katex ) {
|
||||
katex.render( equation, el, {
|
||||
throwOnError: false,
|
||||
displayMode: display,
|
||||
...katexRenderOptions
|
||||
} );
|
||||
}
|
||||
if ( preview ) {
|
||||
moveAndScaleElement( element, el );
|
||||
el.style.visibility = 'visible';
|
||||
}
|
||||
}
|
||||
);
|
||||
} else if ( typeof engine === 'function' ) {
|
||||
engine( equation, element, display );
|
||||
} else {
|
||||
if ( lazyLoad != null ) {
|
||||
try {
|
||||
window.CKEDITOR_MATH_LAZY_LOAD ??= lazyLoad();
|
||||
element.innerHTML = equation;
|
||||
await window.CKEDITOR_MATH_LAZY_LOAD;
|
||||
await renderEquation(
|
||||
equation,
|
||||
element,
|
||||
engine,
|
||||
undefined,
|
||||
display,
|
||||
preview,
|
||||
previewUid,
|
||||
previewClassName,
|
||||
katexRenderOptions
|
||||
);
|
||||
} catch ( err ) {
|
||||
element.innerHTML = equation;
|
||||
console.error(
|
||||
`math-tex-typesetting-lazy-load-failed: Lazy load failed: ${ String( err ) }`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
element.innerHTML = equation;
|
||||
console.warn(
|
||||
`math-tex-typesetting-missing: Missing the mathematical typesetting engine (${ String( engine ) }) for tex.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getBalloonPositionData( editor: Editor ): {
|
||||
target: Range | HTMLElement;
|
||||
positions: Array<PositioningFunction>;
|
||||
} {
|
||||
const view = editor.editing.view;
|
||||
const defaultPositions = BalloonPanelView.defaultPositions;
|
||||
|
||||
const selectedElement = view.document.selection.getSelectedElement();
|
||||
if ( selectedElement ) {
|
||||
return {
|
||||
target: view.domConverter.viewToDom( selectedElement ),
|
||||
positions: [
|
||||
defaultPositions.southArrowNorth,
|
||||
defaultPositions.southArrowNorthWest,
|
||||
defaultPositions.southArrowNorthEast
|
||||
]
|
||||
};
|
||||
} else {
|
||||
const viewDocument = view.document;
|
||||
const firstRange = viewDocument.selection.getFirstRange();
|
||||
if ( !firstRange ) {
|
||||
/**
|
||||
* Missing first range.
|
||||
* @error math-missing-range
|
||||
*/
|
||||
throw new CKEditorError( 'math-missing-range' );
|
||||
}
|
||||
return {
|
||||
target: view.domConverter.viewRangeToDom(
|
||||
firstRange
|
||||
),
|
||||
positions: [
|
||||
defaultPositions.southArrowNorth,
|
||||
defaultPositions.southArrowNorthWest,
|
||||
defaultPositions.southArrowNorthEast
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function selectRenderMode(
|
||||
element: HTMLElement,
|
||||
preview: boolean,
|
||||
previewUid: string,
|
||||
previewClassName: Array<string>,
|
||||
cb: ( previewEl: HTMLElement ) => void
|
||||
) {
|
||||
if ( preview ) {
|
||||
createPreviewElement(
|
||||
element,
|
||||
previewUid,
|
||||
previewClassName,
|
||||
previewEl => {
|
||||
cb( previewEl );
|
||||
}
|
||||
);
|
||||
} else {
|
||||
cb( element );
|
||||
}
|
||||
}
|
||||
|
||||
function renderMathJax3( equation: string, element: HTMLElement, display: boolean, cb: () => void ) {
|
||||
let promiseFunction: undefined | ( ( input: string, options: { display: boolean } ) => Promise<HTMLElement> ) = undefined;
|
||||
if ( !isMathJaxVersion3( MathJax ) ) {
|
||||
return;
|
||||
}
|
||||
if ( MathJax.tex2chtmlPromise ) {
|
||||
promiseFunction = MathJax.tex2chtmlPromise;
|
||||
} else if ( MathJax.tex2svgPromise ) {
|
||||
promiseFunction = MathJax.tex2svgPromise;
|
||||
}
|
||||
|
||||
if ( promiseFunction != null ) {
|
||||
void promiseFunction( equation, { display } ).then( ( node: Element ) => {
|
||||
if ( element.firstChild ) {
|
||||
element.removeChild( element.firstChild );
|
||||
}
|
||||
element.appendChild( node );
|
||||
cb();
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
function renderMathJax2( equation: string, element: HTMLElement, display?: boolean ) {
|
||||
if ( isMathJaxVersion2( MathJax ) ) {
|
||||
if ( display ) {
|
||||
element.innerHTML = '\\[' + equation + '\\]';
|
||||
} else {
|
||||
element.innerHTML = '\\(' + equation + '\\)';
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
MathJax.Hub.Queue(['Typeset', MathJax.Hub, element]);
|
||||
}
|
||||
}
|
||||
|
||||
function createPreviewElement(
|
||||
element: HTMLElement,
|
||||
previewUid: string,
|
||||
previewClassName: Array<string>,
|
||||
render: ( previewEl: HTMLElement ) => void
|
||||
): void {
|
||||
const previewEl = getPreviewElement( element, previewUid, previewClassName );
|
||||
render( previewEl );
|
||||
}
|
||||
|
||||
function getPreviewElement(
|
||||
element: HTMLElement,
|
||||
previewUid: string,
|
||||
previewClassName: Array<string>
|
||||
) {
|
||||
let previewEl = document.getElementById( previewUid );
|
||||
// Create if not found
|
||||
if ( !previewEl ) {
|
||||
previewEl = document.createElement( 'div' );
|
||||
previewEl.setAttribute( 'id', previewUid );
|
||||
previewEl.classList.add( ...previewClassName );
|
||||
previewEl.style.visibility = 'hidden';
|
||||
document.body.appendChild( previewEl );
|
||||
|
||||
let ticking = false;
|
||||
|
||||
const renderTransformation = () => {
|
||||
if ( !ticking ) {
|
||||
window.requestAnimationFrame( () => {
|
||||
if ( previewEl ) {
|
||||
moveElement( element, previewEl );
|
||||
ticking = false;
|
||||
}
|
||||
} );
|
||||
|
||||
ticking = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Create scroll listener for following
|
||||
window.addEventListener( 'resize', renderTransformation );
|
||||
window.addEventListener( 'scroll', renderTransformation );
|
||||
}
|
||||
return previewEl;
|
||||
}
|
||||
|
||||
function moveAndScaleElement( parent: HTMLElement, child: HTMLElement ) {
|
||||
// Move to right place
|
||||
moveElement( parent, child );
|
||||
|
||||
// Scale parent element same as preview
|
||||
const domRect = child.getBoundingClientRect();
|
||||
parent.style.width = domRect.width + 'px';
|
||||
parent.style.height = domRect.height + 'px';
|
||||
}
|
||||
|
||||
function moveElement( parent: HTMLElement, child: HTMLElement ) {
|
||||
const domRect = parent.getBoundingClientRect();
|
||||
const left = window.scrollX + domRect.left;
|
||||
const top = window.scrollY + domRect.top;
|
||||
child.style.position = 'absolute';
|
||||
child.style.left = left + 'px';
|
||||
child.style.top = top + 'px';
|
||||
child.style.zIndex = 'var(--ck-z-panel)';
|
||||
child.style.pointerEvents = 'none';
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg width='68' height='64' viewBox='0 0 68 64' xmlns='http://www.w3.org/2000/svg'><g fill='none' fill-rule='evenodd'><path d='M43.71 11.025a11.508 11.508 0 0 0-1.213 5.159c0 6.42 5.244 11.625 11.713 11.625.083 0 .167 0 .25-.002v16.282a5.464 5.464 0 0 1-2.756 4.739L30.986 60.7a5.548 5.548 0 0 1-5.512 0L4.756 48.828A5.464 5.464 0 0 1 2 44.089V20.344c0-1.955 1.05-3.76 2.756-4.738L25.474 3.733a5.548 5.548 0 0 1 5.512 0l12.724 7.292z' fill='#FFF'/><path d='M45.684 8.79a12.604 12.604 0 0 0-1.329 5.65c0 7.032 5.744 12.733 12.829 12.733.091 0 .183-.001.274-.003v17.834a5.987 5.987 0 0 1-3.019 5.19L31.747 63.196a6.076 6.076 0 0 1-6.037 0L3.02 50.193A5.984 5.984 0 0 1 0 45.003V18.997c0-2.14 1.15-4.119 3.019-5.19L25.71.804a6.076 6.076 0 0 1 6.037 0L45.684 8.79zm-29.44 11.89c-.834 0-1.51.671-1.51 1.498v.715c0 .828.676 1.498 1.51 1.498h25.489c.833 0 1.51-.67 1.51-1.498v-.715c0-.827-.677-1.498-1.51-1.498h-25.49.001zm0 9.227c-.834 0-1.51.671-1.51 1.498v.715c0 .828.676 1.498 1.51 1.498h18.479c.833 0 1.509-.67 1.509-1.498v-.715c0-.827-.676-1.498-1.51-1.498H16.244zm0 9.227c-.834 0-1.51.671-1.51 1.498v.715c0 .828.676 1.498 1.51 1.498h25.489c.833 0 1.51-.67 1.51-1.498v-.715c0-.827-.677-1.498-1.51-1.498h-25.49.001zm41.191-14.459c-5.835 0-10.565-4.695-10.565-10.486 0-5.792 4.73-10.487 10.565-10.487C63.27 3.703 68 8.398 68 14.19c0 5.791-4.73 10.486-10.565 10.486v-.001z' fill='#1EBC61' fill-rule='nonzero'/><path d='M60.857 15.995c0-.467-.084-.875-.251-1.225a2.547 2.547 0 0 0-.686-.88 2.888 2.888 0 0 0-1.026-.531 4.418 4.418 0 0 0-1.259-.175c-.134 0-.283.006-.447.018-.15.01-.3.034-.446.07l.075-1.4h3.587v-1.8h-5.462l-.214 5.06c.319-.116.682-.21 1.089-.28.406-.071.77-.107 1.088-.107.218 0 .437.021.655.063.218.041.413.114.585.218s.313.244.422.419c.109.175.163.391.163.65 0 .424-.132.745-.396.961a1.434 1.434 0 0 1-.938.325c-.352 0-.656-.1-.912-.3-.256-.2-.43-.453-.523-.762l-1.925.588c.1.35.258.664.472.943.214.279.47.514.767.706.298.191.63.339.995.443.365.104.749.156 1.151.156.437 0 .86-.064 1.272-.193.41-.13.778-.323 1.1-.581a2.8 2.8 0 0 0 .775-.981c.193-.396.29-.864.29-1.405h-.001z' fill='#FFF' fill-rule='nonzero'/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
1
packages/ckeditor5-math/theme/icons/math.svg
Normal file
1
packages/ckeditor5-math/theme/icons/math.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
packages/ckeditor5-math/theme/mathform.css
Normal file
35
packages/ckeditor5-math/theme/mathform.css
Normal file
@@ -0,0 +1,35 @@
|
||||
.ck.ck-math-form {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
padding: var(--ck-spacing-standard);
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
flex-wrap: wrap;
|
||||
|
||||
& .ck-math-view {
|
||||
flex-basis: 100%;
|
||||
|
||||
& .ck-labeled-view {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
& .ck-label {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
& .ck-button {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ck-math-tex.ck-placeholder::before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ck.ck-toolbar-container {
|
||||
z-index: calc(var(--ck-z-panel) + 2);
|
||||
}
|
||||
Reference in New Issue
Block a user