数字公式优化
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
// 获取所有 <wmath> 元素
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染 <math>(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 {
|
||||
// 处理全局文档中的 <wmath> 标签
|
||||
} else {
|
||||
const wmathElements = document.querySelectorAll('wmath');
|
||||
wmathElements.forEach((element) => {
|
||||
// 检查 <wmath> 标签是否包含有效的 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);
|
||||
});
|
||||
});
|
||||
|
||||
// 处理全局文档中的 <math> 标签(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 未正确加载!');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -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: '/', //正式
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(/</g, '<');
|
||||
}
|
||||
|
||||
function buildStoredLatex(rawLatex) {
|
||||
const t = String(rawLatex || '').trim();
|
||||
if (!t) return '';
|
||||
if (/^\$\$[\s\S]+\$\$$/.test(t)) return t;
|
||||
if (/^\$[\s\S]+\$$/.test(t)) return `$$${t.replace(/^\$|\$$/g, '')}$$`;
|
||||
return `$$${t}$$`;
|
||||
}
|
||||
|
||||
function resolveWmathWrapMode(latex) {
|
||||
const t = String(latex || '')
|
||||
.trim()
|
||||
.replace(/^\$\$/, '')
|
||||
.replace(/\$\$$/, '');
|
||||
if (!t) return 'block';
|
||||
if (/^\\begin\{/.test(t) || /\n\s*\n/.test(t)) return 'block';
|
||||
if (/\\frac|\\dfrac|\\tfrac|\\sum|\\int|\\prod|\\lim|\\sqrt\{|\\displaystyle/.test(t)) {
|
||||
return 'block';
|
||||
}
|
||||
return 'inline';
|
||||
}
|
||||
|
||||
function createWmathElementHtml(rawLatex, uid, wrapMode) {
|
||||
const storedLatex = buildStoredLatex(rawLatex);
|
||||
const safeLatex = escapeLatexForWmathAttr(storedLatex);
|
||||
const mode = normalizeWmathWrapMode(wrapMode);
|
||||
return (
|
||||
'<wmath contenteditable="false" data-id="' +
|
||||
uid +
|
||||
'" data-latex="' +
|
||||
safeLatex +
|
||||
'" data-wrap="' +
|
||||
mode +
|
||||
'">' +
|
||||
storedLatex +
|
||||
'</wmath>'
|
||||
);
|
||||
}
|
||||
|
||||
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 =
|
||||
'<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="wrap">' +
|
||||
wrapLabel +
|
||||
'</button>';
|
||||
|
||||
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,
|
||||
`<span id="${placeholderId}" class="math-placeholder" style="display:inline-block;min-width:${phWidth}px;min-height:${phHeight}px;vertical-align:middle;">​</span>`
|
||||
);
|
||||
} else {
|
||||
ed.insertContent(
|
||||
`<span id="${placeholderId}" class="math-placeholder" style="display:inline-block;min-width:4px;">​</span>`
|
||||
);
|
||||
}
|
||||
|
||||
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 = `
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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. 创建一个 <wmath> 标签并插入到光标处
|
||||
// const wmathHtml = `<wmath contenteditable="false" data-id="${uid}" data-latex="" data-wrap="block"></wmath>`;
|
||||
// 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. 创建一个 <wmath> 标签并插入到光标处
|
||||
const wmathHtml = `<wmath contenteditable="false" data-id="${uid}" data-latex="" data-wrap="block"></wmath>`;
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
//测试环境
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
</style>
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user