diff --git a/src/assets/css/main.css b/src/assets/css/main.css
index 4e2c344..1c5892a 100644
--- a/src/assets/css/main.css
+++ b/src/assets/css/main.css
@@ -1675,10 +1675,10 @@ wmath {
.tinymce-inline-math-footer {
display: flex;
- flex-wrap: nowrap;
+ flex-wrap: wrap;
align-items: center;
justify-content: space-between;
- gap: 10px;
+ gap: 8px 10px;
padding: 6px 10px 8px;
background: #fff;
}
diff --git a/src/common/js/commonJS.js b/src/common/js/commonJS.js
index 6889ecc..d950ff3 100644
--- a/src/common/js/commonJS.js
+++ b/src/common/js/commonJS.js
@@ -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 =
- '' +
+ '' +
'';
@@ -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 = `
@@ -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);
}
diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js
index b08797b..2ae466b 100644
--- a/src/components/common/langs/en.js
+++ b/src/components/common/langs/en.js
@@ -1083,6 +1083,33 @@ const en = {
importWordMathLoading: 'Importing Word...',
importWordMathSuccess: 'Word imported. Formulas converted to LaTeX.',
importWordMathFail: 'Word import failed. Please check the file format.',
+ importWordMathNoFormula: 'No math formulas found in the Word file.',
+ importWordMathNoNew: 'All formulas already exist. No new rows added.',
+ importWordMathParsed: 'Word parsed: {parsed} formula(s) found, {added} added.',
+ importWordMathParsedNone: 'Word parsed: {parsed} formula(s) found; all already exist.',
+ mathFormula: 'Latex',
+ mathFormulaMore: 'more formulas',
+ latexDataCopy: 'Copy',
+ latexDataCopied: 'Copied',
+ latexDataClickToCopy: 'Click a formula to copy',
+ latexDataAdd: 'Add',
+ latexDataEdit: 'Edit',
+ latexDataDelete: 'Delete',
+ latexDataDeleteAll: 'Clear all',
+ latexDataDeleteAllConfirm: 'Clear all formulas?',
+ latexDataEmpty: 'No formulas yet. Upload Word or add manually.',
+ latexDataNoFormulaDetected: 'No math formulas detected.',
+ latexDataPlaceholder: 'Enter LaTeX formula',
+ latexMathPlaceholder: 'Enter LaTeX code (e.g., \\frac{a}{b}, x^{2}, \\sqrt{x})',
+ latexMathCopyCode: 'Copy LaTeX code',
+ latexMathCopied: 'Copied',
+ latexMathWrapToInline: 'Change to inline with text',
+ latexMathWrapToBlock: 'Change to block display',
+ latexMathEditorTooltip: 'Insert or edit a LaTeX formula',
+ latexMathConfirm: 'OK',
+ latexDataCancel: 'Cancel',
+ latexDataOk: 'Save',
+ editMathFormulaSuccess: 'Math formulas updated successfully.',
selectOne: 'Please select only a single paragraph!',
alreadyCommented: 'There are already annotations in the text, please select again!',
Multicolumn: 'Multicolumn',
diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js
index 25d1c1f..d53ff6c 100644
--- a/src/components/common/langs/zh.js
+++ b/src/components/common/langs/zh.js
@@ -1069,6 +1069,33 @@ const zh = {
importWordMathLoading: '正在导入 Word...',
importWordMathSuccess: 'Word 导入成功,公式已转为 LaTeX',
importWordMathFail: 'Word 导入失败,请检查文件格式',
+ importWordMathNoFormula: '未在 Word 中识别到数学公式',
+ importWordMathNoNew: '公式已存在,未新增重复项',
+ importWordMathParsed: 'Word 解析完成:识别 {parsed} 个公式,新增 {added} 个',
+ importWordMathParsedNone: 'Word 解析完成:识别 {parsed} 个公式,均已存在,未新增',
+ mathFormula: 'Latex',
+ mathFormulaMore: '条公式',
+ latexDataCopy: '复制',
+ latexDataCopied: '已复制',
+ latexDataClickToCopy: '点击数字公式即可复制',
+ latexDataAdd: '新增',
+ latexDataEdit: '修改',
+ latexDataDelete: '删除',
+ latexDataDeleteAll: '清空',
+ latexDataDeleteAllConfirm: '确定清空全部公式吗?',
+ latexDataEmpty: '暂无公式,可上传 Word 或手动新增',
+ latexDataNoFormulaDetected: '暂未检测到数字公式',
+ latexDataPlaceholder: '输入 LaTeX 公式',
+ latexMathPlaceholder: '请输入 LaTeX 代码(如 \\frac{a}{b}、x^{2}、\\sqrt{x})',
+ latexMathCopyCode: '复制 LaTeX 代码',
+ latexMathCopied: '已复制',
+ latexMathWrapToInline: '改为行内公式',
+ latexMathWrapToBlock: '改为独立成行',
+ latexMathEditorTooltip: '插入或编辑 LaTeX 公式',
+ latexMathConfirm: '确定',
+ latexDataCancel: '取消',
+ latexDataOk: '保存',
+ editMathFormulaSuccess: '数字公式更新成功',
selectOne:'请只勾选单个段落!',
alreadyCommented:'文本中已有批注内容请重新选择',
Multicolumn:'多列',
diff --git a/src/components/page/GenerateCharts.vue b/src/components/page/GenerateCharts.vue
index 8361481..bf75d3f 100644
--- a/src/components/page/GenerateCharts.vue
+++ b/src/components/page/GenerateCharts.vue
@@ -64,12 +64,14 @@
@onAddComment="onAddComment"
@addImage="handleImageAdd"
@addTable="handleTableAdd"
+ @importWordMath="handleImportWordMath"
@handlePaperclip="handlePaperclip"
@addComment="addCommentSetting"
@goToComment="goToComment"
@edit="handleFigureAndTableEdit"
@delete="handleFigureAndTableDelete"
@goToListComment="goToListComment"
+ @mathFormulasChange="onMathFormulasChange"
style="width: 100%; height: 100%; padding: 0 0px; box-sizing: border-box; background-color: #fff"
>
@@ -96,6 +98,7 @@
:contentList="Main_List"
:comments="comments"
:wordStyle="wordStyle"
+ :mathFormulasList="mathFormulasList"
@onDrop="onDrop"
@onLinkUnbind="handleUnbindLink"
@onLinkConfirm="handleConfirmLink"
@@ -121,6 +124,8 @@
@onEditTitle="onEditTitle"
@onAddRow="onAddRow"
@changeComment="changeComment"
+ @latexAdded="onLatexAdded"
+ @latexUpdated="onLatexUpdated"
style="width: calc(100%); height: calc(100%)"
:style="`100%`"
>
@@ -417,7 +422,7 @@