数字公式优化
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user