mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-30 03:32:26 +08:00
299 lines
7.5 KiB
TypeScript
299 lines
7.5 KiB
TypeScript
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 ) {
|
|
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( () => {
|
|
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 ) {
|
|
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';
|
|
element.appendChild( previewEl );
|
|
}
|
|
return previewEl;
|
|
}
|