176 lines
5.7 KiB
TypeScript
176 lines
5.7 KiB
TypeScript
/**
|
||
* 数学块扩展
|
||
* 提供数学表达式计算功能,支持实时显示计算结果
|
||
*/
|
||
|
||
import { ViewPlugin, Decoration, WidgetType } from "@codemirror/view";
|
||
import { RangeSetBuilder } from "@codemirror/state";
|
||
import { getNoteBlockFromPos } from "./state";
|
||
import { transactionsHasAnnotation, CURRENCIES_LOADED } from "./annotation";
|
||
// 声明全局math对象
|
||
declare global {
|
||
interface Window {
|
||
math: any;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 数学结果小部件
|
||
*/
|
||
class MathResult extends WidgetType {
|
||
constructor(
|
||
private displayResult: string,
|
||
private copyResult: string
|
||
) {
|
||
super();
|
||
}
|
||
|
||
eq(other: MathResult): boolean {
|
||
return other.displayResult === this.displayResult;
|
||
}
|
||
|
||
toDOM(): HTMLElement {
|
||
const wrap = document.createElement("span");
|
||
wrap.className = "code-blocks-math-result";
|
||
|
||
const inner = document.createElement("span");
|
||
inner.className = "inner";
|
||
inner.innerHTML = this.displayResult;
|
||
wrap.appendChild(inner);
|
||
|
||
inner.addEventListener("click", (e) => {
|
||
e.preventDefault();
|
||
navigator.clipboard.writeText(this.copyResult);
|
||
const copyElement = document.createElement("i");
|
||
copyElement.className = "code-blocks-math-result-copied";
|
||
copyElement.innerHTML = "Copied!";
|
||
wrap.appendChild(copyElement);
|
||
copyElement.offsetWidth; // trigger reflow so that the animation is shown
|
||
copyElement.className = "code-blocks-math-result-copied fade-out";
|
||
setTimeout(() => {
|
||
copyElement.remove();
|
||
}, 1700);
|
||
});
|
||
|
||
return wrap;
|
||
}
|
||
|
||
ignoreEvent(): boolean {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 数学装饰函数
|
||
*/
|
||
function mathDeco(view: any): any {
|
||
const mathParsers = new WeakMap();
|
||
const builder = new RangeSetBuilder();
|
||
|
||
for (const { from, to } of view.visibleRanges) {
|
||
for (let pos = from; pos <= to;) {
|
||
const line = view.state.doc.lineAt(pos);
|
||
const block = getNoteBlockFromPos(view.state, pos);
|
||
|
||
if (block && block.language.name === "math") {
|
||
// get math.js parser and cache it for this block
|
||
let { parser, prev } = mathParsers.get(block) || {};
|
||
if (!parser) {
|
||
// 如果当前可见行不是该 math 块的第一行,为了正确累计 prev,需要从块头开始重新扫描
|
||
if (line.from > block.content.from) {
|
||
pos = block.content.from;
|
||
continue;
|
||
}
|
||
if (typeof window.math !== 'undefined') {
|
||
parser = window.math.parser();
|
||
mathParsers.set(block, { parser, prev });
|
||
}
|
||
}
|
||
|
||
// evaluate math line
|
||
let result: any;
|
||
try {
|
||
if (parser) {
|
||
parser.set("prev", prev);
|
||
result = parser.evaluate(line.text);
|
||
if (result !== undefined) {
|
||
mathParsers.set(block, { parser, prev: result });
|
||
}
|
||
}
|
||
} catch (e) {
|
||
// suppress any errors
|
||
}
|
||
|
||
// if we got a result from math.js, add the result decoration
|
||
if (result !== undefined) {
|
||
const format = parser?.get("format");
|
||
|
||
let resultWidget: MathResult | undefined;
|
||
if (typeof(result) === "string") {
|
||
resultWidget = new MathResult(result, result);
|
||
} else if (format !== undefined && typeof(format) === "function") {
|
||
try {
|
||
resultWidget = new MathResult(format(result), format(result));
|
||
} catch (e) {
|
||
// suppress any errors
|
||
}
|
||
}
|
||
|
||
if (resultWidget === undefined && typeof window.math !== 'undefined') {
|
||
resultWidget = new MathResult(
|
||
window.math.format(result, {
|
||
precision: 8,
|
||
upperExp: 8,
|
||
lowerExp: -6,
|
||
}),
|
||
window.math.format(result, {
|
||
notation: "fixed",
|
||
})
|
||
);
|
||
}
|
||
|
||
if (resultWidget) {
|
||
builder.add(line.to, line.to, Decoration.widget({
|
||
widget: resultWidget,
|
||
side: 1,
|
||
}));
|
||
}
|
||
}
|
||
}
|
||
pos = line.to + 1;
|
||
}
|
||
}
|
||
return builder.finish();
|
||
}
|
||
|
||
/**
|
||
* 数学块视图插件
|
||
*/
|
||
export const mathBlock = ViewPlugin.fromClass(class {
|
||
decorations: any;
|
||
|
||
constructor(view: any) {
|
||
this.decorations = mathDeco(view);
|
||
}
|
||
|
||
update(update: any) {
|
||
// 需要在文档/视口变化或收到 CURRENCIES_LOADED 注解时重新渲染
|
||
if (
|
||
update.docChanged ||
|
||
update.viewportChanged ||
|
||
transactionsHasAnnotation(update.transactions, CURRENCIES_LOADED)
|
||
) {
|
||
this.decorations = mathDeco(update.view);
|
||
}
|
||
}
|
||
}, {
|
||
decorations: v => v.decorations
|
||
});
|
||
|
||
/**
|
||
* 获取数学块扩展
|
||
*/
|
||
export function getMathBlockExtensions() {
|
||
return [mathBlock];
|
||
}
|