diff --git a/package.json b/package.json index a5383bf..f252557 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "katex": "^0.16.21", "mammoth": "^1.5.1", "mathlive": "^0.104.0", + "mathml-to-latex": "^1.5.0", "mavon-editor": "^2.6.17", "multi-items-input": "^0.2.0", "pizzip": "^3.1.7", diff --git a/public/js/global-math.js b/public/js/global-math.js index 11bdf72..4577c79 100644 --- a/public/js/global-math.js +++ b/public/js/global-math.js @@ -8,14 +8,12 @@ window.MathJax = window.MathJax || { displayMath: [['$$', '$$'], ['\\[', '\\]']] }, a11y: { - // 启用 MathJax 可访问性功能,确保使用 aria-label texHints: true, screenReader: true, mathml: { enable: true, }, label: { - // 公式的 aria-label 配置 enable: true, texClass: 'MathJax-Label', form: 'LaTeX' @@ -24,85 +22,71 @@ window.MathJax = window.MathJax || { svg: { fontCache: 'global' } }; +function stripLatexDelimiters(latex) { + return String(latex || '') + .trim() + .replace(/^\$\$/, '') + .replace(/\$\$$/, '') + .replace(/^\$/, '') + .replace(/\$$/, '') + .trim(); +} + +/** 块/行内统一用 $$ 渲染,保证公式大小一致;排版由 data-wrap + CSS 控制 */ +function buildLatexContentForRender(latex) { + const raw = stripLatexDelimiters(latex); + if (!raw) return ''; + return '$$' + raw + '$$'; +} + +function prepareWmathElement(element) { + const latexContent = element.getAttribute('data-latex'); + if (!latexContent) return; + element.innerHTML = buildLatexContentForRender(latexContent); +} + // **定义全局渲染方法** window.renderMathJax = function (tinymceId) { - if (window.MathJax && typeof window.MathJax.typesetPromise === "function") { - // console.log("正在渲染 MathJax 公式..."); - - // 如果提供了 TinyMCE 编辑器 ID + if (window.MathJax && typeof window.MathJax.typesetPromise === 'function') { if (tinymceId) { - const editorInstance = window.tinymce.get(tinymceId); // 根据 ID 获取 TinyMCE 实例 + const editorInstance = window.tinymce.get(tinymceId); if (!editorInstance) return; - + const editorBody = editorInstance.getBody(); - - // 获取所有 元素 const wmathElements = editorBody.querySelectorAll('wmath'); - + if (wmathElements.length > 0) { wmathElements.forEach((element) => { - const latexContent = element.getAttribute('data-latex'); - if (latexContent) { - // 将公式内容填入标签内部 - element.innerHTML = latexContent; - - // 使用 MathJax 渲染该元素 - window.MathJax.typesetPromise([element]).catch((err) => { - console.warn("TinyMCE MathJax 渲染失败:", err); - }); - } + prepareWmathElement(element); + window.MathJax.typesetPromise([element]).catch((err) => { + console.warn('TinyMCE MathJax 渲染失败:', err); + }); }); } - - // 渲染 (MathML)标签,如果有的话 + const mathElements = editorBody.querySelectorAll('math'); if (mathElements.length > 0) { window.MathJax.typesetPromise(Array.from(mathElements)).catch((err) => { - console.warn("MathML 渲染失败:", err); + console.warn('MathML 渲染失败:', err); }); } - } - else { - // 处理全局文档中的 标签 + } else { const wmathElements = document.querySelectorAll('wmath'); wmathElements.forEach((element) => { - // 检查 标签是否包含有效的 data-latex 属性值 - const latexContent = element.getAttribute('data-latex'); - if (latexContent) { - // 将元素的内部内容替换为 data-latex 的值 - element.innerHTML = latexContent; - - // 渲染 MathJax 公式 - window.MathJax.typesetPromise([element]).catch((err) => { - console.warn("MathJax 渲染失败:", err); - }); - } + prepareWmathElement(element); + window.MathJax.typesetPromise([element]).catch((err) => { + console.warn('MathJax 渲染失败:', err); + }); }); - // 处理全局文档中的 标签(MathML 内容) const mathElements = document.querySelectorAll('math'); mathElements.forEach((element) => { - // 渲染 MathJax 公式 window.MathJax.typesetPromise([element]).catch((err) => { - console.warn("MathJax 渲染失败:", err); + console.warn('MathJax 渲染失败:', err); }); }); } - } else { - console.warn("MathJax 未正确加载!"); + console.warn('MathJax 未正确加载!'); } -} - - - - - - - - - - - - - +}; \ No newline at end of file diff --git a/src/api/index.js b/src/api/index.js index 525753d..487fb66 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -19,8 +19,8 @@ const service = axios.create({ // baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换 // baseURL: 'http://www.tougao.com/', //测试本地 记得切换 // baseURL: 'http://192.168.110.110/tougao/public/index.php/', - // baseURL: '/api', //本地 - baseURL: '/', //正式 + baseURL: '/api', //本地 + // baseURL: '/', //正式 }); diff --git a/src/assets/css/main.css b/src/assets/css/main.css index 2cf126f..4e2c344 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -1626,6 +1626,166 @@ wmath { position: fixed !important; } +/* TinyMCE 行内公式输入框(Latex2) */ +.tinymce-inline-math-overlay { + position: fixed; + z-index: 100000; + overflow: visible; +} + +.tinymce-inline-math-panel { + min-width: 360px; + max-width: min(560px, 92vw); + width: max-content; + background: #fff; + border: 1px solid #e8edf5; + border-radius: 10px; + box-shadow: 0 12px 32px rgba(15, 23, 42, 0.12), 0 2px 8px rgba(15, 23, 42, 0.06); + overflow: hidden; +} + +.tinymce-inline-math-field-wrap { + padding: 8px 10px 6px; + background: linear-gradient(180deg, #fafbfd 0%, #f5f7fb 100%); + border-bottom: 1px solid #eef1f6; + max-height: 108px; + overflow: auto; +} + +.tinymce-inline-math-overlay math-field { + display: block; + width: 100%; + min-height: 36px; + padding: 4px 8px; + box-sizing: border-box; + background: #fff; + border: 1px solid #dfe4ec !important; + border-radius: 8px; + box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.04); +} + +.tinymce-inline-math-overlay math-field:focus-within { + border-color: #1654f7 !important; + box-shadow: 0 0 0 3px rgba(22, 84, 247, 0.1); +} + +.tinymce-inline-math-overlay math-field::part(menu-toggle) { + display: none !important; +} + +.tinymce-inline-math-footer { + display: flex; + flex-wrap: nowrap; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 6px 10px 8px; + background: #fff; +} + +.tinymce-inline-math-tools { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: 6px; + flex: 0 1 auto; + min-width: 0; + white-space: nowrap; +} + +.tinymce-inline-math-copy, +.tinymce-inline-math-wrap-toggle { + border: none; + background: transparent; + color: #1654f7; + font-size: 12px; + line-height: 18px; + padding: 2px 0; + cursor: pointer; + white-space: nowrap; +} + +.tinymce-inline-math-copy:hover, +.tinymce-inline-math-wrap-toggle:hover, +.tinymce-inline-math-copy.is-copied { + color: #0f45d4; +} + +.tinymce-inline-math-tool-divider { + display: inline-block; + width: 1px; + height: 14px; + background: #dcdfe6; + flex-shrink: 0; +} + +.tinymce-inline-math-actions { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + +.tinymce-inline-math-confirm, +.tinymce-inline-math-cancel { + height: 28px; + padding: 0 12px; + border-radius: 5px; + font-size: 12px; + cursor: pointer; + line-height: 26px; + white-space: nowrap; +} + +.tinymce-inline-math-confirm { + border: none; + background: #1654f7; + color: #fff; +} + +.tinymce-inline-math-confirm:hover { + background: #0f45d4; +} + +.tinymce-inline-math-cancel { + border: 1px solid #dcdfe6; + background: #fff; + color: #606266; +} + +.tinymce-inline-math-cancel:hover { + border-color: #c0c4cc; + color: #303133; +} + +.tinymce-wmath-context-menu { + position: fixed; + z-index: 100001; + min-width: 200px; + padding: 6px 0; + background: #fff; + border: 1px solid #e4e7ed; + border-radius: 6px; + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); +} + +.tinymce-wmath-context-item { + display: block; + width: 100%; + padding: 8px 16px; + border: none; + background: transparent; + color: #303133; + font-size: 13px; + text-align: left; + cursor: pointer; +} + +.tinymce-wmath-context-item:hover { + background: #f5f7fa; + color: #1654f7; +} + .sticky-header { position: sticky; top: 0; diff --git a/src/common/js/commonJS.js b/src/common/js/commonJS.js index e85e5b6..6889ecc 100644 --- a/src/common/js/commonJS.js +++ b/src/common/js/commonJS.js @@ -3,6 +3,9 @@ import Vue from 'vue'; import katex from 'katex'; import JSZip from 'jszip'; import mammoth from "mammoth"; +import { MathfieldElement } from 'mathlive'; +import 'mathlive/dist/mathlive-static.css'; +import 'mathlive/dist/mathlive-fonts.css'; import { importWordDocumentWithMath as parseWordDocumentWithMath, parseHtmlToLatex as convertHtmlToLatex } from '@/utils/wordMathImport'; import api from '../../api/index.js'; import Common from '@/components/common/common' @@ -26,6 +29,499 @@ const replaceNegativeSign = function (text) { return text; }; +let activeInlineMathOverlay = null; + +function escapeLatexForWmathAttr(latex) { + return String(latex || '') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/' + + storedLatex + + '' + ); +} + +function parseLatexFromWmath(wmathElement) { + const raw = (wmathElement && wmathElement.getAttribute('data-latex')) || ''; + return raw + .replace(/^\$\$/, '') + .replace(/\$\$$/, '') + .replace(/^\$/, '') + .replace(/\$$/, '') + .trim(); +} + +function getEventElementTarget(target) { + if (!target) return null; + if (target.nodeType === 3) return target.parentNode; + if (target.nodeType === 9) return null; + return target; +} + +function findWmathAncestor(node, root) { + let el = getEventElementTarget(node); + while (el && el !== root) { + if (el.nodeType === 1 && el.nodeName && el.nodeName.toLowerCase() === 'wmath') { + return el; + } + el = el.parentNode; + } + return null; +} + +function stripMceSelectedAttr(html) { + return String(html || '').replace(/\s*data-mce-selected="[^"]*"/gi, ''); +} + +function normalizeWmathWrapMode(wrap) { + return wrap === 'inline' ? 'inline' : 'block'; +} + +function getOppositeWrapLabel(wrapMode) { + const target = wrapMode === 'inline' ? 'Text above and below' : 'Inline with text'; + return 'Change to ' + target; +} + +function clearEditorMathSelection(ed) { + if (!ed || !ed.getBody) return; + const body = ed.getBody(); + if (!body) return; + body.querySelectorAll('[data-mce-selected]').forEach((el) => { + el.removeAttribute('data-mce-selected'); + }); + try { + ed.selection.collapse(false); + } catch (err) { + // ignore + } +} + +function closeWmathContextMenu() { + const menu = document.getElementById('tinymce-wmath-context-menu'); + if (menu) menu.remove(); +} + +function copyTextToClipboard(text, onSuccess) { + const value = String(text || '').trim(); + if (!value) return; + const done = typeof onSuccess === 'function' ? onSuccess : () => {}; + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(value).then(done).catch(() => { + const ta = document.createElement('textarea'); + ta.value = value; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + done(); + }); + } else { + const ta = document.createElement('textarea'); + ta.value = value; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + done(); + } +} + +function showWmathContextMenu(ed, wmathElement, event) { + if (!wmathElement || !event) return; + if (document.getElementById('tinymce-inline-math-overlay')) return; + + closeWmathContextMenu(); + event.preventDefault(); + event.stopPropagation(); + + const latex = parseLatexFromWmath(wmathElement); + const wrapMode = wmathElement.getAttribute('data-wrap') || 'block'; + const uid = wmathElement.getAttribute('data-id') || 'wmath-' + Math.random().toString(36).substr(2, 9); + const wrapLabel = getOppositeWrapLabel(wrapMode); + const nextWrap = wrapMode === 'inline' ? 'block' : 'inline'; + + const menu = document.createElement('div'); + menu.id = 'tinymce-wmath-context-menu'; + menu.className = 'tinymce-wmath-context-menu'; + menu.innerHTML = + '' + + ''; + + menu.style.left = Math.min(event.clientX, window.innerWidth - 220) + 'px'; + menu.style.top = Math.min(event.clientY, window.innerHeight - 100) + 'px'; + document.body.appendChild(menu); + + const onMenuAction = (e) => { + const item = e.target.closest('[data-action]'); + if (!item || !menu.contains(item)) return; + e.preventDefault(); + e.stopPropagation(); + const action = item.getAttribute('data-action'); + if (action === 'copy') { + copyTextToClipboard(latex); + } else if (action === 'wrap' && latex) { + ed.dom.setOuterHTML(wmathElement, createWmathElementHtml(latex, uid, nextWrap)); + setTimeout(() => { + if (typeof window.renderMathJax === 'function') { + window.renderMathJax(ed.id); + } + clearEditorMathSelection(ed); + }, 10); + } + closeWmathContextMenu(); + document.removeEventListener('mousedown', onOutside, true); + }; + + const onOutside = (e) => { + if (menu.contains(e.target)) return; + closeWmathContextMenu(); + document.removeEventListener('mousedown', onOutside, true); + }; + + menu.addEventListener('mousedown', (e) => e.stopPropagation()); + menu.addEventListener('click', onMenuAction); + setTimeout(() => document.addEventListener('mousedown', onOutside, true), 0); +} + +function openInlineMathEditor(ed, options) { + try { + openInlineMathEditorCore(ed, options); + } catch (err) { + console.error('openInlineMathEditor failed:', err); + closeInlineMathOverlay(); + } +} + +function openInlineMathEditorCore(ed, options) { + const opts = options || {}; + const mode = opts.mode === 'edit' ? 'edit' : 'insert'; + const wmathElement = opts.wmathElement || null; + + closeInlineMathOverlay(); + + const iframe = ed.iframeElement; + const iframeDoc = ed.getDoc(); + if (!iframe || !iframeDoc) return; + + const placeholderId = 'math-ph-' + Date.now(); + let uid = 'wmath-' + Math.random().toString(36).substr(2, 9); + let initialLatex = ''; + let originalWmathHtml = null; + let preservedWrapMode = null; + let currentWrapMode = 'block'; + + let anchorRect = null; + + if (mode === 'edit' && wmathElement) { + uid = wmathElement.getAttribute('data-id') || uid; + initialLatex = parseLatexFromWmath(wmathElement); + preservedWrapMode = wmathElement.getAttribute('data-wrap') || null; + originalWmathHtml = stripMceSelectedAttr(wmathElement.outerHTML); + anchorRect = wmathElement.getBoundingClientRect(); + const phWidth = Math.max(Math.round(anchorRect.width), 4); + const phHeight = Math.max(Math.round(anchorRect.height), 4); + ed.dom.setOuterHTML( + wmathElement, + `` + ); + } else { + ed.insertContent( + `` + ); + } + + const placeholder = ed.dom.get(placeholderId); + if (!placeholder) return; + + 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); + + let overlay = document.getElementById('tinymce-inline-math-overlay'); + if (!overlay) { + overlay = document.createElement('div'); + overlay.id = 'tinymce-inline-math-overlay'; + overlay.className = 'tinymce-inline-math-overlay'; + document.body.appendChild(overlay); + } + + overlay.style.display = 'block'; + overlay.style.top = overlayTop + 'px'; + overlay.style.left = overlayLeft + 'px'; + overlay.style.minWidth = overlayMinWidth + 'px'; + + overlay.innerHTML = ` +
+
+ +
+ `; + + const fieldWrap = overlay.querySelector('.tinymce-inline-math-field-wrap'); + const confirmBtn = overlay.querySelector('.tinymce-inline-math-confirm'); + const cancelBtn = overlay.querySelector('.tinymce-inline-math-cancel'); + const copyBtn = overlay.querySelector('.tinymce-inline-math-copy'); + const wrapToggleBtn = overlay.querySelector('.tinymce-inline-math-wrap-toggle'); + + const updateWrapToggleLabel = () => { + wrapToggleBtn.textContent = getOppositeWrapLabel(currentWrapMode); + }; + updateWrapToggleLabel(); + + wrapToggleBtn.addEventListener('mousedown', (e) => { + e.preventDefault(); + e.stopPropagation(); + }); + wrapToggleBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + currentWrapMode = currentWrapMode === 'inline' ? 'block' : 'inline'; + updateWrapToggleLabel(); + }); + + copyBtn.addEventListener('mousedown', (e) => { + e.preventDefault(); + e.stopPropagation(); + }); + + const mf = new MathfieldElement(); + fieldWrap.appendChild(mf); + + copyBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + copyTextToClipboard(mf.value, () => { + copyBtn.textContent = 'Copied!'; + copyBtn.classList.add('is-copied'); + setTimeout(() => { + copyBtn.textContent = 'Copy LaTeX code'; + copyBtn.classList.remove('is-copied'); + }, 2000); + }); + }); + + let mfInitialized = false; + const initMathfield = () => { + if (mfInitialized) return; + mfInitialized = true; + mf.virtualKeyboardMode = 'onfocus'; + mf.placeholder = 'Type formula here.'; + mf.menuItems = []; + try { + if (initialLatex) { + mf.value = initialLatex; + } + } catch (valueErr) { + console.warn('Mathfield set value failed:', valueErr); + } + requestAnimationFrame(() => { + mf.focus(); + }); + }; + + mf.addEventListener('mount', initMathfield, { once: true }); + mf.addEventListener('contextmenu', (e) => { + e.preventDefault(); + }); + setTimeout(initMathfield, 0); + + let finalized = false; + let ignoreOutsideClick = true; + const openedAt = Date.now(); + + const cleanupOutsideListeners = () => { + document.removeEventListener('mousedown', onDocMouseDown, true); + iframeDoc.removeEventListener('mousedown', onIframeMouseDown, true); + mf.removeEventListener('blur', onMfBlur); + }; + + const canCancelOutside = () => !ignoreOutsideClick && Date.now() - openedAt > 400; + + const finalize = (save) => { + if (finalized) return; + finalized = true; + cleanupOutsideListeners(); + + const latex = (mf.value || '').trim(); + closeInlineMathOverlay(); + + const ph = ed.dom.get(placeholderId); + if (!ph) return; + + const afterUpdate = () => { + setTimeout(() => { + if (typeof window.renderMathJax === 'function') { + window.renderMathJax(ed.id); + } + clearEditorMathSelection(ed); + }, 10); + }; + + if (save && latex) { + ed.dom.setOuterHTML(ph, createWmathElementHtml(latex, uid, currentWrapMode)); + afterUpdate(); + } else if (mode === 'edit' && originalWmathHtml) { + ed.dom.setOuterHTML(ph, originalWmathHtml); + afterUpdate(); + } else { + ed.dom.remove(ph); + clearEditorMathSelection(ed); + } + }; + + const onDocMouseDown = (e) => { + if (!canCancelOutside()) return; + if (overlay.contains(e.target)) return; + if (isMathliveUiTarget(e.target)) return; + finalize(false); + }; + + const onIframeMouseDown = (e) => { + if (!canCancelOutside()) return; + if (overlay.contains(e.target)) return; + if (isMathliveUiTarget(e.target)) return; + finalize(false); + }; + + const onMfBlur = () => { + setTimeout(() => { + if (finalized) return; + if (!canCancelOutside()) return; + const activeEl = document.activeElement; + const iframeActiveEl = iframeDoc.activeElement; + if (overlay.contains(activeEl)) return; + if (isMathliveUiTarget(activeEl) || isMathliveUiTarget(iframeActiveEl)) return; + finalize(false); + }, 150); + }; + + confirmBtn.addEventListener('mousedown', (e) => { + e.preventDefault(); + e.stopPropagation(); + }); + confirmBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + finalize(true); + }); + + cancelBtn.addEventListener('mousedown', (e) => { + e.preventDefault(); + e.stopPropagation(); + }); + cancelBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + finalize(false); + }); + + activeInlineMathOverlay = { + overlay, + iframeDoc, + mf, + finalize, + cleanupOutsideListeners + }; + + mf.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + e.preventDefault(); + finalize(false); + } + }); + mf.addEventListener('blur', onMfBlur); + + setTimeout(() => { + ignoreOutsideClick = false; + document.addEventListener('mousedown', onDocMouseDown, true); + iframeDoc.addEventListener('mousedown', onIframeMouseDown, true); + }, 500); +} + +function insertInlineMathWithMathlive(ed) { + openInlineMathEditor(ed, { mode: 'insert' }); +} + +function editInlineMathWithMathlive(ed, wmathElement) { + if (!wmathElement) return; + if (activeInlineMathOverlay) return; + openInlineMathEditor(ed, { mode: 'edit', wmathElement }); +} + +function isMathliveUiTarget(target) { + if (!target || !target.closest) return false; + return !!( + target.closest('#tinymce-inline-math-overlay') || + target.closest('.ML__keyboard') || + target.closest('.ML__virtual-keyboard') || + target.closest('.MLK__plate') || + target.closest('math-field') + ); +} + +function closeInlineMathOverlay() { + if (!activeInlineMathOverlay) return; + const { overlay, cleanupOutsideListeners } = activeInlineMathOverlay; + if (typeof cleanupOutsideListeners === 'function') { + cleanupOutsideListeners(); + } + overlay.style.display = 'none'; + overlay.innerHTML = ''; + activeInlineMathOverlay = null; +} + // 首字母大写的方法 // const capitalizeFirstLetter = function (text) { @@ -1837,6 +2333,19 @@ str = str.replace(regex, function (match, content, offset, fullString) { }, + insertInlineMathWithMathlive(ed) { + openInlineMathEditor(ed, { mode: 'insert' }); + }, + editInlineMathWithMathlive(ed, wmathElement) { + if (!wmathElement) return; + openInlineMathEditor(ed, { mode: 'edit', wmathElement }); + }, + findWmathFromTarget(target, root) { + return findWmathAncestor(target, root); + }, + openWmathContextMenu(ed, wmathElement, event) { + showWmathContextMenu(ed, wmathElement, event); + }, initEditorButton(vueInstance, ed) { ed.ui.registry.addMenuButton('customDropdown', { @@ -2189,28 +2698,37 @@ str = str.replace(regex, function (match, content, offset, fullString) { let latexEditorBookmark = null; // 用于记录插入点 let activeEditorId = null; // 当前激活的编辑器 ID - // 在编辑器工具栏中添加 "LateX" 按钮 + // // 在编辑器工具栏中添加 "LateX" 按钮 + // ed.ui.registry.addButton('LateX', { + // text: 'LateX', // 按钮文本 + // onAction: function () { + // // 1. 获取当前光标位置 + // const latexEditorBookmark = ed.selection.getBookmark(2); // 获取光标位置 + // const editorId = ed.id; // 保存当前编辑器 ID + + // // 2. 生成一个随机的 ID,用于 wmath 标签 + // const uid = 'wmath-' + Math.random().toString(36).substr(2, 9); + + // // 3. 创建一个 标签并插入到光标处 + // const wmathHtml = ``; + // ed.insertContent(wmathHtml); // 在光标位置插入 wmath 标签 + + // // 4. 打开公式编辑器窗口,并传递光标位置、编辑器 ID 和 wmathId + // const url = `/LateX?editorId=${editorId}&wmathId=${uid}`; + // // vueInstance.openLatexEditor({ + // // editorId:editorId, + // // wmathId:uid, + // // }); + // window.open(url, '_blank', 'width=1000,height=800,scrollbars=no,resizable=no'); + // } + // }); + + // 行内公式编辑(MathLive),类似 Word「在此处键入公式」 ed.ui.registry.addButton('LateX', { - text: 'LateX', // 按钮文本 + text: 'LateX', + tooltip: 'Type formula here', onAction: function () { - // 1. 获取当前光标位置 - const latexEditorBookmark = ed.selection.getBookmark(2); // 获取光标位置 - const editorId = ed.id; // 保存当前编辑器 ID - - // 2. 生成一个随机的 ID,用于 wmath 标签 - const uid = 'wmath-' + Math.random().toString(36).substr(2, 9); - - // 3. 创建一个 标签并插入到光标处 - const wmathHtml = ``; - ed.insertContent(wmathHtml); // 在光标位置插入 wmath 标签 - - // 4. 打开公式编辑器窗口,并传递光标位置、编辑器 ID 和 wmathId - const url = `/LateX?editorId=${editorId}&wmathId=${uid}`; - // vueInstance.openLatexEditor({ - // editorId:editorId, - // wmathId:uid, - // }); - window.open(url, '_blank', 'width=1000,height=800,scrollbars=no,resizable=no'); + insertInlineMathWithMathlive(ed); } }); diff --git a/src/components/common/common.vue b/src/components/common/common.vue index 9bac70a..a1781ec 100644 --- a/src/components/common/common.vue +++ b/src/components/common/common.vue @@ -2,14 +2,14 @@ //记得切换 //正式 -const mediaUrl = '/public/'; -const baseUrl = '/'; +// const mediaUrl = '/public/'; +// const baseUrl = '/'; //正式环境 -// const mediaUrl = 'https://submission.tmrjournals.com/public/'; -// // const mediaUrl = 'http://zmzm.tougao.dev.com/public/'; -// const baseUrl = '/api' +const mediaUrl = 'https://submission.tmrjournals.com/public/'; +// const mediaUrl = 'http://zmzm.tougao.dev.com/public/'; +const baseUrl = '/api' //测试环境 diff --git a/src/components/page/GenerateCharts.vue b/src/components/page/GenerateCharts.vue index 4896df4..8361481 100644 --- a/src/components/page/GenerateCharts.vue +++ b/src/components/page/GenerateCharts.vue @@ -2614,5 +2614,10 @@ export default { wmath[data-wrap='inline'] { display: inline-block !important; width: auto !important; + vertical-align: middle; +} +wmath[data-wrap='inline'] mjx-container { + display: inline-block !important; + margin: 0 2px !important; } diff --git a/src/components/page/components/Tinymce/index.vue b/src/components/page/components/Tinymce/index.vue index 6490f58..03e4ea1 100644 --- a/src/components/page/components/Tinymce/index.vue +++ b/src/components/page/components/Tinymce/index.vue @@ -641,6 +641,18 @@ export default { font-weight: bold !important; } + wmath { + pointer-events: auto !important; + cursor: pointer !important; + outline: none !important; + } + + wmath[contenteditable="false"] { + cursor: pointer !important; + outline: none !important; + box-shadow: none !important; + } + @keyframes blueGlow { 0%, 100% { @@ -731,35 +743,24 @@ export default { e.preventDefault(); _this.pasteClipboardAsMath(ed); }); - var currentWmathElement = null; + ed.on('click', function (e) { - const wmathElement = e.target.closest('wmath'); + const wmathElement = _this.$commonJS.findWmathFromTarget + ? _this.$commonJS.findWmathFromTarget(e.target, ed.getBody()) + : null; if (wmathElement) { - currentWmathElement = wmathElement; // 保存当前点击的元素 + e.preventDefault(); + e.stopPropagation(); + _this.$commonJS.editInlineMathWithMathlive(ed, wmathElement); + } + }); - // 1. 提取内容:获取 LaTeX - const latexContentRaw = wmathElement.getAttribute('data-latex') || ''; - const latexContent = latexContentRaw.replace(/\$/g, '').trim(); - const encoded = encodeURIComponent(latexContent); - - // 2. 提取状态:获取之前的 wrap 模式 👈 重要新增 - const wrapMode = wmathElement.getAttribute('data-wrap') || 'block'; - - // 3. 处理唯一 ID - let wmathId = wmathElement.getAttribute('data-id'); - if (!wmathId) { - wmathId = 'wmath-' + Math.random().toString(36).substr(2, 9); - wmathElement.setAttribute('data-id', wmathId); - } - - const editorId = ed.id; - - // 4. 打开窗口:在 URL 参数中增加 wrap 模式 👈 这样弹窗就知道该默认选哪个了 - window.open( - `/LateX?id=${encoded}&wmathId=${wmathId}&editorId=${editorId}&wrap=${wrapMode}`, - '_blank', - 'width=1000,height=800,scrollbars=no,resizable=no' - ); + ed.on('contextmenu', function (e) { + const wmathElement = _this.$commonJS.findWmathFromTarget + ? _this.$commonJS.findWmathFromTarget(e.target, ed.getBody()) + : null; + if (wmathElement) { + _this.$commonJS.openWmathContextMenu(ed, wmathElement, e); } }); diff --git a/src/components/page/components/table/DynamicTable.vue b/src/components/page/components/table/DynamicTable.vue index 5e06a47..73a0f40 100644 --- a/src/components/page/components/table/DynamicTable.vue +++ b/src/components/page/components/table/DynamicTable.vue @@ -240,4 +240,29 @@ export default { .table-fade-enter-active, .table-fade-leave-active { transition: opacity 0.3s ease; } .table-fade-enter, .table-fade-leave-to { opacity: 0; } + +/* 表格预览:行内公式与文字同一行 */ +.table-paper-view ::v-deep wmath[data-wrap='inline'] { + display: inline !important; + width: auto !important; + vertical-align: middle; +} + +.table-paper-view ::v-deep wmath[data-wrap='inline'] mjx-container { + display: inline-block !important; + width: auto !important; + margin: 0 2px !important; + vertical-align: middle; +} + +.table-paper-view ::v-deep wmath[data-wrap='inline'] mjx-container[display='true'] { + display: inline-block !important; + margin: 0 2px !important; + font-size: 12px !important; +} + +.table-paper-view ::v-deep wmath[data-wrap='block'] { + display: block; + width: 100%; +} \ No newline at end of file diff --git a/src/components/page/components/table/content.vue b/src/components/page/components/table/content.vue index bd9782b..f051d9f 100644 --- a/src/components/page/components/table/content.vue +++ b/src/components/page/components/table/content.vue @@ -16,7 +16,7 @@ :value="value" :typesettingType="typesettingType" class="paste-area text-container" - :toolbar="!isAutomaticUpdate?['bold italic |customBlue removeBlue|LateX| myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton|searchreplace']:['bold italic |customBlue removeBlue| myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton|searchreplace']" + :toolbar="!isAutomaticUpdate?['bold italic |customBlue removeBlue|LateX Latex2| myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton|searchreplace']:['bold italic |customBlue removeBlue|Latex2| myuppercase myuppercasea Line MoreSymbols|subscript superscript|clearButton|searchreplace']" style=" /* white-space: pre-line; */ line-height: 12px; diff --git a/src/utils/tinymceStyles.js b/src/utils/tinymceStyles.js index 7f85222..e256ef0 100644 --- a/src/utils/tinymceStyles.js +++ b/src/utils/tinymceStyles.js @@ -1,8 +1,26 @@ export const tableStyle = ` wmath[data-wrap="inline"] { + display: inline !important; + width: auto !important; + vertical-align: middle; +} +wmath[data-wrap="inline"] mjx-container { display: inline-block !important; width: auto !important; - + margin: 0 2px !important; + vertical-align: middle; + font-size: 14px !important; +} +wmath[data-wrap="inline"] mjx-container[display="true"] { + display: inline-block !important; + margin: 0 2px !important; +} +wmath[data-wrap="block"] { + display: flex; + width: 100%; +} +wmath[data-wrap="block"] mjx-container { + font-size: 14px !important; } .word-img-placeholder { background: #f0f0f0 ; @@ -128,10 +146,10 @@ mjx-container { font-size: 14px !important; ; } -wmath{ +wmath[data-wrap="block"] { width: 100%; -display: block; display: flex; +margin-bottom: 1em; } Info{ color:#ff8f25;