Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/extensions/additional/Math/Math.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {ExtensionsManager} from '../../../core';
import {BaseNode, BaseSchemaSpecs} from '../../base/specs';

import {MathNode, MathSpecs} from './MathSpecs';
import {isLatexMode, parseLatexFormulas} from './utils';

const {
schema,
Expand Down Expand Up @@ -47,3 +48,46 @@ describe('Math extension', () => {
same(`$$${formula}$$\n\n`, doc(mathB(formula)));
});
});

describe('latex-paste-plugin utilities', () => {
describe('isLatexMode', () => {
it('should return true for tex/latex modes', () => {
expect(isLatexMode('tex')).toBe(true);
expect(isLatexMode('latex')).toBe(true);
expect(isLatexMode('bibtex')).toBe(true);
});

it('should return false for non-latex modes', () => {
expect(isLatexMode('javascript')).toBe(false);
expect(isLatexMode('python')).toBe(false);
expect(isLatexMode(undefined)).toBe(false);
});
});

describe('parseLatexFormulas', () => {
it('should split formulas by double newlines', () => {
const input = 'E = mc^2\n\ne^{i\\pi} + 1 = 0';
const result = parseLatexFormulas(input);
expect(result).toEqual(['E = mc^2', 'e^{i\\pi} + 1 = 0']);
});

it('should preserve comment lines starting with %', () => {
const input = `% Einstein equation
E = mc^2

% Euler formula
e^{i\\pi} + 1 = 0`;
const result = parseLatexFormulas(input);
expect(result).toEqual([
'% Einstein equation\nE = mc^2',
'% Euler formula\ne^{i\\pi} + 1 = 0',
]);
});

it('should return comments as formulas', () => {
const input = '% Comment 1\n% Comment 2';
const result = parseLatexFormulas(input);
expect(result).toEqual(['% Comment 1\n% Comment 2']);
});
});
});
11 changes: 11 additions & 0 deletions src/extensions/additional/Math/const.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
export * from './MathSpecs/const';
export {mathBType, mathIType} from './MathSpecs';

export const LATEX_MODES = new Set([
'tex',
'latex',
'bibtex',
'doctex',
'latex-expl3',
'pweave',
'jlweave',
'rsweave',
]);
3 changes: 3 additions & 0 deletions src/extensions/additional/Math/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import {
removeEmptyMathInlineIfCursorIsAtBeginning,
} from './commands';
import {mathBType, mathIType} from './const';
import {latexPastePlugin} from './latex-paste-plugin';
import {type MathNodeViewOptions, mathViewAndEditPlugin} from './view-and-edit';

import './index.scss';

export {MathNode, mathBType, mathIType} from './MathSpecs';
export {MathBlockNodeView, MathInlineNodeView} from './view-and-edit';
export {isLatexMode, parseLatexFormulas} from './utils';

const mathIAction = 'addMathInline';
const mathBAction = 'toMathBlock';
Expand All @@ -45,6 +47,7 @@ export const Math: ExtensionAuto<MathOptions> = (builder, opts) => {
}));

builder
.addPlugin(latexPastePlugin, builder.Priority.High)
.addPlugin(() =>
mathViewAndEditPlugin({
...opts,
Expand Down
58 changes: 58 additions & 0 deletions src/extensions/additional/Math/latex-paste-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {Plugin} from 'prosemirror-state';

import {getLoggerFromState} from '#core';
import {Fragment} from '#pm/model';

import {MathNode} from './const';
import {getLatexData, parseLatexFormulas} from './utils';

export const latexPastePlugin = () =>
new Plugin({
props: {
handleDOMEvents: {
paste(view, e: Event) {
const event = e as ClipboardEvent;
if (!event.clipboardData || view.state.selection.$from.parent.type.spec.code) {
return false;
}

const latexData = getLatexData(event.clipboardData);
if (!latexData) return false;

getLoggerFromState(view.state).event({
domEvent: 'paste',
event: 'paste-latex-from-code-editor',
editor: latexData.editor,
editorMode: latexData.mode,
empty: !latexData.value,
dataTypes: event.clipboardData.types,
});

const {tr, schema} = view.state;
const mathBlockType = schema.nodes[MathNode.Block];

if (!mathBlockType) return false;

if (latexData.value) {
const formulas = parseLatexFormulas(latexData.value);

if (formulas.length > 0) {
const nodes = formulas.map((formula) =>
mathBlockType.create(null, schema.text(formula)),
);
const fragment = Fragment.from(nodes);
tr.replaceWith(tr.selection.from, tr.selection.to, fragment);
} else {
tr.replaceWith(tr.selection.from, tr.selection.to, Fragment.empty);
}
} else {
tr.replaceWith(tr.selection.from, tr.selection.to, Fragment.empty);
}

view.dispatch(tr.scrollIntoView());
e.preventDefault();
return true;
},
},
},
});
49 changes: 49 additions & 0 deletions src/extensions/additional/Math/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import dd from 'ts-dedent';

import {DataTransferType, isVSCode, tryParseVSCodeData} from 'src/utils/clipboard';

import {LATEX_MODES} from './const';

export function isLatexMode(mode: string | undefined): boolean {
if (!mode) return false;
return LATEX_MODES.has(mode.toLowerCase());
}

export function parseLatexFormulas(content: string): string[] {
const blocks = content.split(/\n\s*\n/);
const formulas: string[] = [];

for (const block of blocks) {
const lines = block
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0);

if (lines.length > 0) {
formulas.push(lines.join('\n'));
}
}

return formulas;
}

export function getLatexData(
data: DataTransfer,
): null | {editor: string; mode: string; value: string} {
if (!data.getData(DataTransferType.Text)) return null;

if (isVSCode(data)) {
const vsCodeData = tryParseVSCodeData(data);
const mode = vsCodeData?.mode;

if (mode && isLatexMode(mode)) {
return {
editor: 'vscode',
mode,
value: dd(data.getData(DataTransferType.Text)),
};
}
}

return null;
}
5 changes: 5 additions & 0 deletions src/extensions/markdown/CodeBlock/handle-paste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import dd from 'ts-dedent';
import {getLoggerFromState} from '#core';
import {Fragment} from '#pm/model';
import type {EditorProps} from '#pm/view';
import {isLatexMode} from 'src/extensions/additional/Math/utils';
import {DataTransferType, isVSCode, tryParseVSCodeData} from 'src/utils/clipboard';

import {CodeBlockNodeAttr} from './CodeBlockSpecs';
Expand Down Expand Up @@ -44,6 +45,10 @@ function getCodeData(data: DataTransfer): null | {editor: string; mode?: string;
if (isVSCode(data)) {
editor = 'vscode';
mode = tryParseVSCodeData(data)?.mode;

if (isLatexMode(mode)) {
return null;
}
} else return null;

return {editor, mode, value: dd(data.getData(DataTransferType.Text))};
Expand Down
Loading