tijiao
This commit is contained in:
@@ -1078,6 +1078,11 @@ const en = {
|
||||
selectComment: 'Please select the text to add annotations to!',
|
||||
selectLinkText: 'Please select the target text content before associating it with a figure or table.',
|
||||
selectWord: 'Please select only a single word!',
|
||||
importWordMath: 'Word',
|
||||
importWordMathTip: 'Upload Word and parse math formulas',
|
||||
importWordMathLoading: 'Importing Word...',
|
||||
importWordMathSuccess: 'Word imported. Formulas converted to LaTeX.',
|
||||
importWordMathFail: 'Word import failed. Please check the file format.',
|
||||
selectOne: 'Please select only a single paragraph!',
|
||||
alreadyCommented: 'There are already annotations in the text, please select again!',
|
||||
Multicolumn: 'Multicolumn',
|
||||
|
||||
@@ -1064,6 +1064,11 @@ const zh = {
|
||||
selectComment: '请选择要添加批注的文本',
|
||||
selectLinkText: '执行图表关联前,请先选定目标文本内容',
|
||||
selectWord:'请只选中单个单词!',
|
||||
importWordMath: 'Word',
|
||||
importWordMathTip: '上传 Word 并解析数学公式',
|
||||
importWordMathLoading: '正在导入 Word...',
|
||||
importWordMathSuccess: 'Word 导入成功,公式已转为 LaTeX',
|
||||
importWordMathFail: 'Word 导入失败,请检查文件格式',
|
||||
selectOne:'请只勾选单个段落!',
|
||||
alreadyCommented:'文本中已有批注内容请重新选择',
|
||||
Multicolumn:'多列',
|
||||
|
||||
@@ -129,14 +129,17 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
|
||||
handler(val) {
|
||||
if (!this.hasChange && this.hasInit) {
|
||||
this.$nextTick(() => {
|
||||
window.tinymce.get(this.tinymceId).setContent(val);
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
|
||||
|
||||
if (!this.hasChange && this.hasInit) {
|
||||
this.$nextTick(() => {
|
||||
window.tinymce.get(this.tinymceId).setContent(val);
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -484,10 +487,7 @@ export default {
|
||||
|
||||
if (!this.isPlainLatexText(normalized)) return '';
|
||||
|
||||
const lines = normalized
|
||||
.split(/\n/)
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean);
|
||||
const lines = normalized.split(/\n/).map((l) => l.trim()).filter(Boolean);
|
||||
if (lines.length > 1 && lines.every((l) => this.isPlainLatexText(l))) {
|
||||
return lines.map((l) => this.createWmathHtml(l, this.resolveWmathWrapMode(l))).join('');
|
||||
}
|
||||
@@ -531,86 +531,68 @@ export default {
|
||||
return new Blob([u8arr], { type: mime });
|
||||
},
|
||||
formatHtml(val) {
|
||||
const rawValue = val || ''; // 处理 null
|
||||
const cleanEmptyTags = /<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g;
|
||||
const replaceSpaces = /\s+(?=<)|(?<=>)\s+/g;
|
||||
const removeBr = /<br\s*\/?>/gi; // 移除所有 br 标签
|
||||
const rawValue = val || ''; // 处理 null
|
||||
const cleanEmptyTags = /<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g;
|
||||
const replaceSpaces = /\s+(?=<)|(?<=>)\s+/g;
|
||||
const removeBr = /<br\s*\/?>/gi; // 移除所有 br 标签
|
||||
|
||||
if (rawValue.includes('wordTableHtml')) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(rawValue, 'text/html');
|
||||
const cells = doc.querySelectorAll('td, th');
|
||||
if (rawValue.includes('wordTableHtml')) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(rawValue, 'text/html');
|
||||
const cells = doc.querySelectorAll('td, th');
|
||||
|
||||
cells.forEach((cell) => {
|
||||
cell.innerHTML = cell.innerHTML
|
||||
.replace(cleanEmptyTags, '')
|
||||
.replace(removeBr, '') // 针对你“不想要br”的需求
|
||||
.replace(replaceSpaces, ' ');
|
||||
});
|
||||
return doc.body.innerHTML;
|
||||
} else {
|
||||
return rawValue
|
||||
.replace(cleanEmptyTags, '')
|
||||
.replace(removeBr, '')
|
||||
.replace(replaceSpaces, ' ');
|
||||
}
|
||||
},
|
||||
getSafeContent(val) {
|
||||
const rawValue = val || '';
|
||||
const cleanEmptyTags = /<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g;
|
||||
const replaceSpaces = /\s+(?=<)|(?<=>)\s+/g;
|
||||
|
||||
cells.forEach((cell) => {
|
||||
cell.innerHTML = cell.innerHTML
|
||||
.replace(cleanEmptyTags, '')
|
||||
.replace(removeBr, '') // 针对你“不想要br”的需求
|
||||
.replace(replaceSpaces, ' ');
|
||||
});
|
||||
return doc.body.innerHTML;
|
||||
} else {
|
||||
return rawValue.replace(cleanEmptyTags, '').replace(removeBr, '').replace(replaceSpaces, ' ');
|
||||
}
|
||||
},
|
||||
getSafeContent(val) {
|
||||
const rawValue = val || '';
|
||||
const cleanEmptyTags = /<([a-zA-Z1-6]+)\b[^>]*><\/\1>/g;
|
||||
const replaceSpaces = /\s+(?=<)|(?<=>)\s+/g;
|
||||
|
||||
const escapeIllegalLT = (str) => {
|
||||
return str.replace(/<(?!(\/?(p|div|span|table|tr|td|th|b|i|strong|em|ul|ol|li|br|img|myh3|myfigure|mytable|wmath)))/gi, '<');
|
||||
};
|
||||
|
||||
const escapeIllegalLT = (str) => {
|
||||
return str.replace(
|
||||
/<(?!(\/?(p|div|span|table|tr|td|th|b|i|strong|em|ul|ol|li|br|img|myh3|myfigure|mytable|wmath)))/gi,
|
||||
'<'
|
||||
);
|
||||
};
|
||||
let processedHtml = '';
|
||||
|
||||
let processedHtml = '';
|
||||
if (rawValue.includes('wordTableHtml')) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(rawValue, 'text/html');
|
||||
const cells = doc.querySelectorAll('td, th');
|
||||
|
||||
if (rawValue.includes('wordTableHtml')) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(rawValue, 'text/html');
|
||||
const cells = doc.querySelectorAll('td, th');
|
||||
cells.forEach((cell) => {
|
||||
|
||||
let cellText = cell.innerHTML;
|
||||
|
||||
cell.innerHTML = cellText
|
||||
.replace(cleanEmptyTags, '')
|
||||
.replace(replaceSpaces, ' ');
|
||||
});
|
||||
processedHtml = doc.body.innerHTML;
|
||||
} else {
|
||||
|
||||
processedHtml = rawValue
|
||||
.replace(cleanEmptyTags, '')
|
||||
.replace(replaceSpaces, ' ');
|
||||
}
|
||||
|
||||
cells.forEach((cell) => {
|
||||
let cellText = cell.innerHTML;
|
||||
return processedHtml;
|
||||
},
|
||||
|
||||
cell.innerHTML = cellText.replace(cleanEmptyTags, '').replace(replaceSpaces, ' ');
|
||||
});
|
||||
processedHtml = doc.body.innerHTML;
|
||||
} else {
|
||||
processedHtml = rawValue.replace(cleanEmptyTags, '').replace(replaceSpaces, ' ');
|
||||
}
|
||||
|
||||
return processedHtml;
|
||||
},
|
||||
parseWordLinearTextToStandardLatex(text) {
|
||||
let result = text;
|
||||
|
||||
// 🌟 动态特征一:修复分式与包裹大括号(如把 13(...) 转换为 \frac{1}{3}\left( ... \right))
|
||||
// 通过捕捉连续的两位数字和紧跟的括号,不管它是 12 还是 13 还是 25,动态拆分为分子分母
|
||||
result = result.replace(/^(\d)(\d)\s*\(([\s\S]+)\)$/g, '\\frac{$1}{$2}\\left( $3 \\right)');
|
||||
|
||||
// 🌟 动态特征二:学术公式多变量复合下标高精度自动恢复(如 MIi 变成 \widehat{\text{MI}}_i)
|
||||
// 抽象规律:大写字母组成的复合单词(代表一个统计变量),后面紧跟着一个用于做索引循环的小写字母(如 i, j, k, n)
|
||||
// 我们用正则边界 \b 精准识别这种大小写交替的数学边界,动态完成 \text 包裹和 _ 下标追加
|
||||
result = result.replace(/\b([A-Z]{2,})([ijkmn])\b/g, '\\widehat{\\text{$1 exterior_flag}}_$2');
|
||||
result = result.replace(/ exterior_flag/g, ''); // 清理临时标记
|
||||
|
||||
// 🌟 动态特征三:单字母变量的帽子与下标高精度自动恢复(如 Fi 变成 \widehat{\text{F}}_i)
|
||||
// 抽象规律:单个大写字母后面紧跟单个小写字母索引。同时通过排除机制 (?!SGMS) 确保左边的复合变量不被错误套上帽子
|
||||
result = result.replace(/\b(?!SGMS)([A-Z])([ijkmn\d])\b/g, '\\widehat{\\text{$1 single_flag}}_$2');
|
||||
result = result.replace(/ single_flag/g, '');
|
||||
|
||||
// 🌟 动态特征四:左侧纯主变量的普通下标处理(如 SGMSi 变成 \text{SGMS}_i,不需要加帽子)
|
||||
// 匹配任何在等号左侧或独立区域的、不需要戴帽子的复合纯文本下标变量
|
||||
result = result.replace(/\b([A-Z]{3,})([ijkmn\d])\b/g, '\\text{$1}_$2');
|
||||
|
||||
// 🌟 动态特征五:基础数学连字符规范化
|
||||
result = result.replace(/\s*\*\s*/g, ' \\cdot ');
|
||||
|
||||
return result;
|
||||
},
|
||||
initTinymce() {
|
||||
|
||||
var _this = this;
|
||||
window.tinymce.init({
|
||||
..._this.tinymceOtherInit,
|
||||
@@ -783,56 +765,6 @@ export default {
|
||||
|
||||
ed.on('paste', async (event) => {
|
||||
const rtf = event.clipboardData.getData('text/rtf');
|
||||
console.log('🚀 ~ setup ~ rtf:', rtf);
|
||||
|
||||
let plainText = event.clipboardData.getData('text/plain') || '';
|
||||
|
||||
// ========================================================
|
||||
// 1. 【通用公式内核判定】—— 绝不绑定任何具体公式的字母
|
||||
// ========================================================
|
||||
// 只要 RTF 中包含微软 Office 原生公式对象标记(objdata / mmath / object),
|
||||
// 并且纯文本里有数学等号,说明当前用户复制的 100% 是一个公式对象,而不是普通插图!
|
||||
const isWordFormulaObject =
|
||||
rtf.includes('\\rtf') &&
|
||||
(rtf.includes('objdata') || rtf.includes('\\mmath') || rtf.includes('\\object')) &&
|
||||
plainText.includes('=');
|
||||
|
||||
if (isWordFormulaObject) {
|
||||
// 【第一步:绝对截胡断流】
|
||||
// 强行扼杀浏览器的默认粘贴行为,并阻止事件冒泡。
|
||||
// 这会使下方原本属于普通插图的图片转换、接口上传、绿色进度条等代码直接被彻底绕过!
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let finalLatex = '';
|
||||
|
||||
// 如果用户复制时,纯文本里已经带有标准 LaTeX 控制符了(比如 \frac, \hat 等)
|
||||
if (/\\frac|\\hat|\\sqrt|\\alpha/i.test(plainText)) {
|
||||
finalLatex = plainText.trim();
|
||||
}
|
||||
// 【核心动态翻译引擎】如果拿到的只是被 Word 阉割后的线性纯文本,
|
||||
// 我们基于通用的“数学结构特征”进行全自动高保真翻译,不写死任何具体的变量名!
|
||||
else if (plainText.trim()) {
|
||||
finalLatex = parseWordLinearTextToStandardLatex(plainText.trim());
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// 【高保真 LaTeX 公式注入】
|
||||
// ========================================================
|
||||
if (finalLatex) {
|
||||
// 清洗掉首尾可能重复的 $ 符号
|
||||
finalLatex = finalLatex.replace(/^(\$\$?)|(\$\$?)$/g, '').trim();
|
||||
|
||||
// 包装成标准的块级公式
|
||||
const latexContainer = `$$${finalLatex}$$`;
|
||||
|
||||
ed.insertContent(latexContainer);
|
||||
console.log('【通用架构级公式转换成功】:', latexContainer);
|
||||
}
|
||||
|
||||
return; // 【绝杀】直接中断整个 paste 函数,下面你原有的图片代码直接气化,绝不触发上传!
|
||||
}
|
||||
|
||||
if (rtf && rtf.includes('\\pict')) {
|
||||
const extracted = extractHexImagesFromRTF(rtf);
|
||||
_this.totalUploadImages = extracted.length; // 设置总数
|
||||
@@ -898,36 +830,40 @@ export default {
|
||||
}
|
||||
});
|
||||
ed.on('init', function () {
|
||||
_this.editorInstance = ed;
|
||||
_this.hasInit = true;
|
||||
_this.$commonJS.inTinymceButtonClass();
|
||||
if (_this.isAutomaticUpdate) {
|
||||
_this.$emit('updateChange', _this.value);
|
||||
}
|
||||
_this.content = _this.getSafeContent(_this.value);
|
||||
_this.editorInstance = ed;
|
||||
_this.hasInit = true;
|
||||
_this.$commonJS.inTinymceButtonClass();
|
||||
if (_this.isAutomaticUpdate) {
|
||||
_this.$emit('updateChange', _this.value);
|
||||
|
||||
_this.handleSetContent(_this.content || '');
|
||||
|
||||
// 3. 监听内容变化
|
||||
ed.on('NodeChange Change KeyUp SetContent', () => {
|
||||
_this.hasChange = true;
|
||||
_this.$emit('input', ed.getContent({ format: 'raw' }));
|
||||
});
|
||||
}
|
||||
_this.content = _this.getSafeContent(_this.value);
|
||||
|
||||
// 4. 监听 DOM 变化
|
||||
const observer = new MutationObserver(() => {
|
||||
const currentContent = ed.getContent({ format: 'raw' });
|
||||
if (_this.isAutomaticUpdate) {
|
||||
_this.$emit('updateChange', currentContent);
|
||||
}
|
||||
});
|
||||
_this.handleSetContent(_this.content || '');
|
||||
|
||||
observer.observe(ed.getBody(), {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true
|
||||
});
|
||||
});
|
||||
// 3. 监听内容变化
|
||||
ed.on('NodeChange Change KeyUp SetContent', () => {
|
||||
_this.hasChange = true;
|
||||
_this.$emit('input', ed.getContent({ format: 'raw' }));
|
||||
});
|
||||
|
||||
// 4. 监听 DOM 变化
|
||||
const observer = new MutationObserver(() => {
|
||||
const currentContent = ed.getContent({ format: 'raw' });
|
||||
if (_this.isAutomaticUpdate) {
|
||||
_this.$emit('updateChange', currentContent);
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(ed.getBody(), {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true
|
||||
});
|
||||
});
|
||||
|
||||
// 定义自定义按钮
|
||||
ed.ui.registry.addButton('customButtonExportWord', {
|
||||
@@ -985,6 +921,7 @@ export default {
|
||||
|
||||
if (tempDiv.querySelector('table')) {
|
||||
if (_this.type == 'table') {
|
||||
|
||||
_this.$commonJS.parseTableToArray(content, (tableList) => {
|
||||
var contentHtml = `
|
||||
<div class="thumbnailTableBox wordTableHtml table_Box table_Box3333" style="">
|
||||
@@ -1014,7 +951,7 @@ export default {
|
||||
container.innerHTML = contentHtml;
|
||||
args.content = container.innerHTML; // 更新处理后的内容
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const plainText = (tempDiv.textContent || tempDiv.innerText || '').trim();
|
||||
const builtPlain = _this.buildWmathHtmlFromLatexText(plainText);
|
||||
|
||||
Reference in New Issue
Block a user