数字公式优化

This commit is contained in:
2026-06-01 17:59:22 +08:00
parent 4b1a10b20c
commit 08aa3327db
13 changed files with 2526 additions and 89 deletions

View File

@@ -30,6 +30,19 @@ const replaceNegativeSign = function (text) {
};
let activeInlineMathOverlay = null;
let mathEditorVue = null;
function bindMathEditorI18n(vueInstance) {
mathEditorVue = vueInstance || null;
}
function tCommonMath(key, fallback) {
if (mathEditorVue && typeof mathEditorVue.$t === 'function') {
const translated = mathEditorVue.$t(key);
if (translated && translated !== key) return translated;
}
return fallback;
}
function escapeLatexForWmathAttr(latex) {
return String(latex || '')
@@ -113,8 +126,10 @@ function normalizeWmathWrapMode(wrap) {
}
function getOppositeWrapLabel(wrapMode) {
const target = wrapMode === 'inline' ? 'Text above and below' : 'Inline with text';
return 'Change to ' + target;
if (wrapMode === 'inline') {
return tCommonMath('commonTable.latexMathWrapToBlock', 'Change to block display');
}
return tCommonMath('commonTable.latexMathWrapToInline', 'Change to inline with text');
}
function clearEditorMathSelection(ed) {
@@ -179,7 +194,9 @@ function showWmathContextMenu(ed, wmathElement, event) {
menu.id = 'tinymce-wmath-context-menu';
menu.className = 'tinymce-wmath-context-menu';
menu.innerHTML =
'<button type="button" class="tinymce-wmath-context-item" data-action="copy">Copy LaTeX code</button>' +
'<button type="button" class="tinymce-wmath-context-item" data-action="copy">' +
tCommonMath('commonTable.latexMathCopyCode', 'Copy LaTeX code') +
'</button>' +
'<button type="button" class="tinymce-wmath-context-item" data-action="wrap">' +
wrapLabel +
'</button>';
@@ -229,6 +246,69 @@ function openInlineMathEditor(ed, options) {
}
}
/** 将编辑器内元素的 getBoundingClientRect 转为页面视口坐标(兼容 inline / iframe */
function getElementViewportRect(element) {
if (!element) return null;
const rect = element.getBoundingClientRect();
const doc = element.ownerDocument;
const win = doc && doc.defaultView;
if (!win || win === window) {
return rect;
}
const frameEl = win.frameElement;
if (!frameEl) return rect;
const frameRect = frameEl.getBoundingClientRect();
return {
top: frameRect.top + rect.top,
left: frameRect.left + rect.left,
bottom: frameRect.top + rect.bottom,
right: frameRect.left + rect.right,
width: rect.width,
height: rect.height
};
}
/** 将行内公式面板固定在视口内(表格/宽文档右侧单元格也能完整操作) */
function positionInlineMathOverlay(overlay, anchorRect) {
if (!overlay || !anchorRect) return;
const margin = 12;
const gap = 6;
const vw = window.innerWidth || document.documentElement.clientWidth;
const vh = window.innerHeight || document.documentElement.clientHeight;
overlay.style.display = 'block';
overlay.style.right = 'auto';
overlay.style.bottom = 'auto';
overlay.style.maxWidth = Math.max(280, vw - margin * 2) + 'px';
const panelRect = overlay.getBoundingClientRect();
const panelW = panelRect.width || 380;
const panelH = panelRect.height || 160;
let top = anchorRect.bottom + gap;
if (top + panelH + margin > vh) {
top = anchorRect.top - panelH - gap;
}
if (top < margin) {
top = Math.max(margin, Math.min(anchorRect.top, vh - panelH - margin));
}
if (top + panelH + margin > vh) {
top = Math.max(margin, vh - panelH - margin);
}
let left = anchorRect.left;
if (left + panelW + margin > vw) {
left = vw - panelW - margin;
}
if (left < margin) {
left = margin;
}
overlay.style.top = Math.round(top) + 'px';
overlay.style.left = Math.round(left) + 'px';
}
function openInlineMathEditorCore(ed, options) {
const opts = options || {};
const mode = opts.mode === 'edit' ? 'edit' : 'insert';
@@ -236,9 +316,8 @@ function openInlineMathEditorCore(ed, options) {
closeInlineMathOverlay();
const iframe = ed.iframeElement;
const iframeDoc = ed.getDoc();
if (!iframe || !iframeDoc) return;
if (!iframeDoc) return;
const placeholderId = 'math-ph-' + Date.now();
let uid = 'wmath-' + Math.random().toString(36).substr(2, 9);
@@ -254,7 +333,7 @@ function openInlineMathEditorCore(ed, options) {
initialLatex = parseLatexFromWmath(wmathElement);
preservedWrapMode = wmathElement.getAttribute('data-wrap') || null;
originalWmathHtml = stripMceSelectedAttr(wmathElement.outerHTML);
anchorRect = wmathElement.getBoundingClientRect();
anchorRect = getElementViewportRect(wmathElement);
const phWidth = Math.max(Math.round(anchorRect.width), 4);
const phHeight = Math.max(Math.round(anchorRect.height), 4);
ed.dom.setOuterHTML(
@@ -272,12 +351,9 @@ function openInlineMathEditorCore(ed, options) {
currentWrapMode = normalizeWmathWrapMode(preservedWrapMode);
const phRect = placeholder.getBoundingClientRect();
const iframeRect = iframe.getBoundingClientRect();
const anchor = anchorRect || phRect;
const overlayTop = iframeRect.top + anchor.top;
const overlayLeft = iframeRect.left + anchor.left;
const overlayMinWidth = Math.min(Math.max(anchor.width, phRect.width, 360), 560);
const phRect = getElementViewportRect(placeholder);
const anchorViewportRect = anchorRect || phRect;
const overlayMinWidth = Math.min(Math.max(anchorViewportRect.width, phRect.width, 320), 520);
let overlay = document.getElementById('tinymce-inline-math-overlay');
if (!overlay) {
@@ -287,23 +363,24 @@ function openInlineMathEditorCore(ed, options) {
document.body.appendChild(overlay);
}
overlay.style.display = 'block';
overlay.style.top = overlayTop + 'px';
overlay.style.left = overlayLeft + 'px';
overlay.style.minWidth = overlayMinWidth + 'px';
const copyLabel = tCommonMath('commonTable.latexMathCopyCode', 'Copy LaTeX code');
const cancelLabel = tCommonMath('commonTable.latexDataCancel', 'Cancel');
const okLabel = tCommonMath('commonTable.latexMathConfirm', 'OK');
overlay.innerHTML = `
<div class="tinymce-inline-math-panel">
<div class="tinymce-inline-math-field-wrap"></div>
<div class="tinymce-inline-math-footer">
<div class="tinymce-inline-math-tools">
<button type="button" class="tinymce-inline-math-copy">Copy LaTeX code</button>
<button type="button" class="tinymce-inline-math-copy">${copyLabel}</button>
<span class="tinymce-inline-math-tool-divider"></span>
<button type="button" class="tinymce-inline-math-wrap-toggle"></button>
</div>
<div class="tinymce-inline-math-actions">
<button type="button" class="tinymce-inline-math-cancel">Cancel</button>
<button type="button" class="tinymce-inline-math-confirm">OK</button>
<button type="button" class="tinymce-inline-math-cancel">${cancelLabel}</button>
<button type="button" class="tinymce-inline-math-confirm">${okLabel}</button>
</div>
</div>
</div>
@@ -324,12 +401,6 @@ function openInlineMathEditorCore(ed, options) {
e.preventDefault();
e.stopPropagation();
});
wrapToggleBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
currentWrapMode = currentWrapMode === 'inline' ? 'block' : 'inline';
updateWrapToggleLabel();
});
copyBtn.addEventListener('mousedown', (e) => {
e.preventDefault();
@@ -343,10 +414,10 @@ function openInlineMathEditorCore(ed, options) {
e.preventDefault();
e.stopPropagation();
copyTextToClipboard(mf.value, () => {
copyBtn.textContent = 'Copied!';
copyBtn.textContent = tCommonMath('commonTable.latexMathCopied', 'Copied');
copyBtn.classList.add('is-copied');
setTimeout(() => {
copyBtn.textContent = 'Copy LaTeX code';
copyBtn.textContent = copyLabel;
copyBtn.classList.remove('is-copied');
}, 2000);
});
@@ -357,7 +428,10 @@ function openInlineMathEditorCore(ed, options) {
if (mfInitialized) return;
mfInitialized = true;
mf.virtualKeyboardMode = 'onfocus';
mf.placeholder = 'Type formula here.';
mf.placeholder = tCommonMath(
'commonTable.latexMathPlaceholder',
'Enter LaTeX code (e.g., \\frac{a}{b}, x^{2}, \\sqrt{x})'
);
mf.menuItems = [];
try {
if (initialLatex) {
@@ -377,6 +451,16 @@ function openInlineMathEditorCore(ed, options) {
});
setTimeout(initMathfield, 0);
const repositionOverlay = () => {
const livePh = ed.dom.get(placeholderId);
const rect = livePh ? getElementViewportRect(livePh) : anchorViewportRect;
positionInlineMathOverlay(overlay, rect);
};
repositionOverlay();
requestAnimationFrame(repositionOverlay);
window.addEventListener('scroll', repositionOverlay, true);
window.addEventListener('resize', repositionOverlay);
let finalized = false;
let ignoreOutsideClick = true;
const openedAt = Date.now();
@@ -385,6 +469,8 @@ function openInlineMathEditorCore(ed, options) {
document.removeEventListener('mousedown', onDocMouseDown, true);
iframeDoc.removeEventListener('mousedown', onIframeMouseDown, true);
mf.removeEventListener('blur', onMfBlur);
window.removeEventListener('scroll', repositionOverlay, true);
window.removeEventListener('resize', repositionOverlay);
};
const canCancelOutside = () => !ignoreOutsideClick && Date.now() - openedAt > 400;
@@ -467,6 +553,16 @@ function openInlineMathEditorCore(ed, options) {
finalize(false);
});
wrapToggleBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
currentWrapMode = currentWrapMode === 'inline' ? 'block' : 'inline';
updateWrapToggleLabel();
const latex = (mf.value || '').trim() || initialLatex;
if (!latex) return;
finalize(true);
});
activeInlineMathOverlay = {
overlay,
iframeDoc,
@@ -491,12 +587,25 @@ function openInlineMathEditorCore(ed, options) {
}
function insertInlineMathWithMathlive(ed) {
if (activeInlineMathOverlay) {
const mf = activeInlineMathOverlay.mf;
if (mf && typeof mf.focus === 'function') {
mf.focus();
}
return;
}
openInlineMathEditor(ed, { mode: 'insert' });
}
function editInlineMathWithMathlive(ed, wmathElement) {
if (!wmathElement) return;
if (activeInlineMathOverlay) return;
if (activeInlineMathOverlay) {
const mf = activeInlineMathOverlay.mf;
if (mf && typeof mf.focus === 'function') {
mf.focus();
}
return;
}
openInlineMathEditor(ed, { mode: 'edit', wmathElement });
}
@@ -2347,6 +2456,7 @@ str = str.replace(regex, function (match, content, offset, fullString) {
showWmathContextMenu(ed, wmathElement, event);
},
initEditorButton(vueInstance, ed) {
bindMathEditorI18n(vueInstance);
ed.ui.registry.addMenuButton('customDropdown', {
text: 'Set Title', // 下拉框标题
@@ -2726,7 +2836,9 @@ str = str.replace(regex, function (match, content, offset, fullString) {
// 行内公式编辑MathLive类似 Word「在此处键入公式」
ed.ui.registry.addButton('LateX', {
text: 'LateX',
tooltip: 'Type formula here',
tooltip: vueInstance.$t
? vueInstance.$t('commonTable.latexMathEditorTooltip')
: 'Insert or edit a LaTeX formula',
onAction: function () {
insertInlineMathWithMathlive(ed);
}