mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 10:02:59 +08:00
Merge pull request #2003 from TriliumNext/math-edit
feat(math): support multi-line formula editing
This commit is contained in:
commit
6a29fae7c0
@ -27,6 +27,18 @@ export default class MathEditing extends Plugin {
|
|||||||
|
|
||||||
public init(): void {
|
public init(): void {
|
||||||
const editor = this.editor;
|
const editor = this.editor;
|
||||||
|
|
||||||
|
const originalProcessor = editor.data.processor;
|
||||||
|
const originalToView = originalProcessor.toView.bind(originalProcessor);
|
||||||
|
const mathSpanRegex = /<span class="math-tex">([\s\S]*?)<\/span>/g;
|
||||||
|
originalProcessor.toView = (data: string) => {
|
||||||
|
// Preprocessing: preserve line breaks inside math formulas by replacing \n with <!--LF-->
|
||||||
|
const processedData = data.replace(mathSpanRegex, (_, content) =>
|
||||||
|
`<span class="math-tex">${content.replace(/\n/g, '___MATH_TEX_LF___')}</span>`
|
||||||
|
);
|
||||||
|
return originalToView(processedData);
|
||||||
|
};
|
||||||
|
|
||||||
editor.commands.add( 'math', new MathCommand( editor ) );
|
editor.commands.add( 'math', new MathCommand( editor ) );
|
||||||
|
|
||||||
this._defineSchema();
|
this._defineSchema();
|
||||||
@ -120,8 +132,7 @@ export default class MathEditing extends Plugin {
|
|||||||
model: ( viewElement, { writer } ) => {
|
model: ( viewElement, { writer } ) => {
|
||||||
const child = viewElement.getChild( 0 );
|
const child = viewElement.getChild( 0 );
|
||||||
if ( child?.is( '$text' ) ) {
|
if ( child?.is( '$text' ) ) {
|
||||||
const equation = child.data.trim();
|
const equation = child.data.trim().replace(/___MATH_TEX_LF___/g, '\n');
|
||||||
|
|
||||||
const params = Object.assign( extractDelimiters( equation ), {
|
const params = Object.assign( extractDelimiters( equation ), {
|
||||||
type: mathConfig.forceOutputType ?
|
type: mathConfig.forceOutputType ?
|
||||||
mathConfig.outputType :
|
mathConfig.outputType :
|
||||||
|
@ -110,6 +110,27 @@ export default class MathUI extends Plugin {
|
|||||||
cancel();
|
cancel();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
// Allow pressing Enter to submit changes, and use Shift+Enter to insert a new line
|
||||||
|
formView.keystrokes.set('enter', (data, cancel) => {
|
||||||
|
if (!data.shiftKey) {
|
||||||
|
formView.fire('submit');
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allow the textarea to be resizable
|
||||||
|
formView.mathInputView.fieldView.once('render', () => {
|
||||||
|
const textarea = formView.mathInputView.fieldView.element;
|
||||||
|
if (!textarea) return;
|
||||||
|
textarea.focus();
|
||||||
|
Object.assign(textarea.style, {
|
||||||
|
resize: 'both',
|
||||||
|
height: '100px',
|
||||||
|
width: '400px',
|
||||||
|
minWidth: '100%',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return formView;
|
return formView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { ButtonView, createLabeledInputText, FocusCycler, LabelView, LabeledFieldView, submitHandler, SwitchButtonView, View, ViewCollection, type InputTextView, type FocusableView, Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5';
|
import { ButtonView, createLabeledTextarea, FocusCycler, LabelView, LabeledFieldView, submitHandler, SwitchButtonView, View, ViewCollection, type TextareaView, type FocusableView, Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5';
|
||||||
import { IconCheck, IconCancel } from "@ckeditor/ckeditor5-icons";
|
import { IconCheck, IconCancel } from "@ckeditor/ckeditor5-icons";
|
||||||
import { extractDelimiters, hasDelimiters } from '../utils.js';
|
import { extractDelimiters, hasDelimiters } from '../utils.js';
|
||||||
import MathView from './mathview.js';
|
import MathView from './mathview.js';
|
||||||
import '../../theme/mathform.css';
|
import '../../theme/mathform.css';
|
||||||
import type { KatexOptions } from '../typings-external.js';
|
import type { KatexOptions } from '../typings-external.js';
|
||||||
|
|
||||||
class MathInputView extends LabeledFieldView<InputTextView> {
|
class MathInputView extends LabeledFieldView<TextareaView> {
|
||||||
public value: null | string = null;
|
public value: null | string = null;
|
||||||
public isReadOnly = false;
|
public isReadOnly = false;
|
||||||
|
|
||||||
constructor( locale: Locale ) {
|
constructor( locale: Locale ) {
|
||||||
super( locale, createLabeledInputText );
|
super( locale, createLabeledTextarea );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export default class MathView extends View {
|
|||||||
this.setTemplate( {
|
this.setTemplate( {
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
attributes: {
|
attributes: {
|
||||||
class: [ 'ck', 'ck-math-preview' ]
|
class: [ 'ck', 'ck-math-preview', 'ck-reset_all-excluded' ]
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,6 @@ export async function renderEquation(
|
|||||||
el => {
|
el => {
|
||||||
renderMathJax3( equation, el, display, () => {
|
renderMathJax3( equation, el, display, () => {
|
||||||
if ( preview ) {
|
if ( preview ) {
|
||||||
moveAndScaleElement( element, el );
|
|
||||||
el.style.visibility = 'visible';
|
el.style.visibility = 'visible';
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
@ -115,7 +114,6 @@ export async function renderEquation(
|
|||||||
if ( preview && isMathJaxVersion2( MathJax ) ) {
|
if ( preview && isMathJaxVersion2( MathJax ) ) {
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
MathJax.Hub.Queue( () => {
|
MathJax.Hub.Queue( () => {
|
||||||
moveAndScaleElement( element, el );
|
|
||||||
el.style.visibility = 'visible';
|
el.style.visibility = 'visible';
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
@ -139,7 +137,6 @@ export async function renderEquation(
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
if ( preview ) {
|
if ( preview ) {
|
||||||
moveAndScaleElement( element, el );
|
|
||||||
el.style.visibility = 'visible';
|
el.style.visibility = 'visible';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -295,47 +292,7 @@ function getPreviewElement(
|
|||||||
previewEl.setAttribute( 'id', previewUid );
|
previewEl.setAttribute( 'id', previewUid );
|
||||||
previewEl.classList.add( ...previewClassName );
|
previewEl.classList.add( ...previewClassName );
|
||||||
previewEl.style.visibility = 'hidden';
|
previewEl.style.visibility = 'hidden';
|
||||||
document.body.appendChild( previewEl );
|
element.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;
|
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';
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user