|
|
@@ -59,6 +59,7 @@ import { getRandomNumber } from '@/utils';
|
|
|
import { isNodeType } from '@/utils/validate';
|
|
|
import { fileUpload } from '@/api/app';
|
|
|
import { addTone, handleToneValue } from '@/utils/common';
|
|
|
+import { fontFormats, getSharedFontFacesCss } from '@/common/data';
|
|
|
|
|
|
export default {
|
|
|
name: 'RichText',
|
|
|
@@ -152,6 +153,7 @@ export default {
|
|
|
left: 0,
|
|
|
},
|
|
|
id: getRandomNumber(),
|
|
|
+ editor: null,
|
|
|
editorIsInited: false,
|
|
|
editorBeforeInitConfig: {},
|
|
|
init: {
|
|
|
@@ -160,6 +162,7 @@ export default {
|
|
|
font-family: 'League';
|
|
|
src: url('${process.env.BASE_URL}static/font/pinyin.ttf');
|
|
|
}
|
|
|
+ ${getSharedFontFacesCss()}
|
|
|
mjx-assistive-mml {
|
|
|
position: absolute !important;
|
|
|
width: 1px !important;
|
|
|
@@ -229,6 +232,8 @@ export default {
|
|
|
|
|
|
let isRendered = false; // 标记是否已渲染
|
|
|
editor.on('init', () => {
|
|
|
+ this.editor = editor;
|
|
|
+ editor.getBody().style.backgroundColor = '#f2f3f5'; // 设置编辑器背景色
|
|
|
editor.getBody().style.fontSize = this.init.font_size; // 设置默认字体大小
|
|
|
editor.getBody().style.fontFamily = this.init.font_family; // 设置默认字体
|
|
|
editor.getBody().style.color = this.fontColor; // 设置默认字体颜色
|
|
|
@@ -496,12 +501,12 @@ export default {
|
|
|
text: '●',
|
|
|
tooltip: '着重点',
|
|
|
onAction: () => {
|
|
|
- const editor = tinymce.get(this.id);
|
|
|
+ if (!this.editor) return;
|
|
|
|
|
|
- if (editor.formatter.match('emphasisDot')) {
|
|
|
- editor.formatter.remove('emphasisDot');
|
|
|
+ if (this.editor.formatter.match('emphasisDot')) {
|
|
|
+ this.editor.formatter.remove('emphasisDot');
|
|
|
} else {
|
|
|
- editor.formatter.apply('emphasisDot');
|
|
|
+ this.editor.formatter.apply('emphasisDot');
|
|
|
}
|
|
|
},
|
|
|
});
|
|
|
@@ -663,13 +668,7 @@ export default {
|
|
|
}, 500);
|
|
|
});
|
|
|
},
|
|
|
- font_formats:
|
|
|
- '楷体=楷体,微软雅黑;' +
|
|
|
- '黑体=黑体,微软雅黑;' +
|
|
|
- '宋体=宋体,微软雅黑;' +
|
|
|
- 'Arial=arial,helvetica,sans-serif;' +
|
|
|
- 'Times New Roman=times new roman,times,serif;' +
|
|
|
- '拼音=League;',
|
|
|
+ font_formats: fontFormats,
|
|
|
fontsize_formats: '8pt 10pt 12pt 14pt 16pt 18pt 20pt 22pt 24pt 26pt 28pt 30pt 32pt 34pt 36pt',
|
|
|
// 字数限制
|
|
|
ax_wordlimit_num: this.wordlimitNum,
|
|
|
@@ -702,9 +701,9 @@ export default {
|
|
|
isViewNote: {
|
|
|
handler(newVal) {
|
|
|
if (newVal) {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (editor) {
|
|
|
- let start = editor.selection.getStart();
|
|
|
+ const start = editor.selection.getStart();
|
|
|
this.$emit('selectNote', start.getAttribute('data-annotation-id'));
|
|
|
}
|
|
|
}
|
|
|
@@ -712,13 +711,12 @@ export default {
|
|
|
},
|
|
|
fontSize: {
|
|
|
handler(newVal) {
|
|
|
- const editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (!editor || typeof editor.execCommand !== 'function') return;
|
|
|
|
|
|
const applyFontSize = () => {
|
|
|
try {
|
|
|
editor.execCommand('FontSize', false, newVal);
|
|
|
- editor.setContent(this.value); // 触发内容更新,解决 value 在设置后变为空字符串的问题
|
|
|
} catch (e) {
|
|
|
// 容错:某些情况下 execCommand 会抛错,忽略即可
|
|
|
// console.warn('apply fontSize failed', e);
|
|
|
@@ -735,13 +733,12 @@ export default {
|
|
|
},
|
|
|
fontFamily: {
|
|
|
handler(newVal) {
|
|
|
- const editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (!editor || typeof editor.execCommand !== 'function') return;
|
|
|
|
|
|
const applyFontFamily = () => {
|
|
|
try {
|
|
|
editor.execCommand('FontName', false, newVal);
|
|
|
- editor.setContent(this.value); // 触发内容更新,解决 value 在设置后变为空字符串的问题
|
|
|
} catch (e) {
|
|
|
// 容错:忽略因 selection 不可用或其它原因导致的错误
|
|
|
}
|
|
|
@@ -787,7 +784,6 @@ export default {
|
|
|
if (this.isFill || this.isViewNote) {
|
|
|
window.addEventListener('click', this.hideContentmenu);
|
|
|
}
|
|
|
- this.setBackgroundColor();
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
if (this.pageFrom !== 'audit') {
|
|
|
@@ -797,13 +793,14 @@ export default {
|
|
|
if (this.isFill || this.isViewNote) {
|
|
|
window.removeEventListener('click', this.hideContentmenu);
|
|
|
}
|
|
|
+ this.editor = null;
|
|
|
},
|
|
|
methods: {
|
|
|
getRichContent() {
|
|
|
- return tinymce.get(this.id).getContent();
|
|
|
+ return this.editor?.getContent() || '';
|
|
|
},
|
|
|
getRichSelectionContent() {
|
|
|
- return tinymce.get(this.id).selection.getContent();
|
|
|
+ return this.editor?.selection?.getContent() || '';
|
|
|
},
|
|
|
displayToolbar(isTitle, isInit) {
|
|
|
if (!this.editorIsInited) {
|
|
|
@@ -811,7 +808,7 @@ export default {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (!editor) return;
|
|
|
const header = editor.editorContainer?.querySelector('.tox-editor-header');
|
|
|
if (header) {
|
|
|
@@ -832,26 +829,26 @@ export default {
|
|
|
},
|
|
|
|
|
|
smartPreserveLineBreaks(editor, content) {
|
|
|
- let body = editor.getBody();
|
|
|
- let originalParagraphs = Array.from(body.getElementsByTagName('p'));
|
|
|
+ const body = editor.getBody();
|
|
|
+ const originalParagraphs = Array.from(body.getElementsByTagName('p'));
|
|
|
|
|
|
- let tempDiv = document.createElement('div');
|
|
|
- tempDiv.innerHTML = content;
|
|
|
- let outputParagraphs = Array.from(tempDiv.getElementsByTagName('p'));
|
|
|
+ const parser = new DOMParser();
|
|
|
+ const doc = parser.parseFromString(content, 'text/html');
|
|
|
+ const outputParagraphs = Array.from(doc.getElementsByTagName('p'));
|
|
|
|
|
|
outputParagraphs.forEach((outputP, index) => {
|
|
|
- let originalP = originalParagraphs[index];
|
|
|
+ const originalP = originalParagraphs[index];
|
|
|
|
|
|
if (originalP && outputP.innerHTML === '') {
|
|
|
// 判断这个空段落是否应该包含 <br>
|
|
|
- let shouldHaveBr = this.shouldPreserveLineBreak(originalP, index, originalParagraphs);
|
|
|
+ const shouldHaveBr = this.shouldPreserveLineBreak(originalP, index, originalParagraphs);
|
|
|
if (shouldHaveBr) {
|
|
|
outputP.innerHTML = '<br>';
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- return tempDiv.innerHTML;
|
|
|
+ return doc.body.innerHTML;
|
|
|
},
|
|
|
|
|
|
shouldPreserveLineBreak(paragraph, index, allParagraphs) {
|
|
|
@@ -862,8 +859,8 @@ export default {
|
|
|
|
|
|
// 规则2:如果段落位于内容中间(不是第一个或最后一个)
|
|
|
if (index > 0 && index < allParagraphs.length - 1) {
|
|
|
- let prevHasContent = allParagraphs[index - 1].textContent.trim() !== '';
|
|
|
- let nextHasContent = allParagraphs[index + 1].textContent.trim() !== '';
|
|
|
+ const prevHasContent = allParagraphs[index - 1].textContent.trim() !== '';
|
|
|
+ const nextHasContent = allParagraphs[index + 1].textContent.trim() !== '';
|
|
|
|
|
|
if (prevHasContent && nextHasContent) {
|
|
|
return true;
|
|
|
@@ -884,24 +881,21 @@ export default {
|
|
|
|
|
|
// 设置背景色
|
|
|
setBackgroundColor() {
|
|
|
- let iframes = document.getElementsByTagName('iframe');
|
|
|
- for (let i = 0; i < iframes.length; i++) {
|
|
|
- let iframe = iframes[i];
|
|
|
- // 获取 <iframe> 内部的文档对象
|
|
|
- let iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
|
|
|
- let bodyElement = iframeDocument.body;
|
|
|
- if (bodyElement) {
|
|
|
- // 设置背景色
|
|
|
- bodyElement.style.backgroundColor = '#f2f3f5';
|
|
|
- }
|
|
|
+ const editor = this.editor;
|
|
|
+ if (!editor) return;
|
|
|
+ const body = editor.getBody();
|
|
|
+ if (body) {
|
|
|
+ body.style.backgroundColor = '#f2f3f5';
|
|
|
}
|
|
|
},
|
|
|
+
|
|
|
/**
|
|
|
* 判断内容是否全部加粗
|
|
|
*/
|
|
|
isAllBold() {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
- let body = editor.getBody();
|
|
|
+ const editor = this.editor;
|
|
|
+ if (!editor) return false;
|
|
|
+ const body = editor.getBody();
|
|
|
function getTextNodes(node) {
|
|
|
let textNodes = [];
|
|
|
if (node.nodeType === 3 && node.nodeValue.trim() !== '') {
|
|
|
@@ -913,7 +907,7 @@ export default {
|
|
|
}
|
|
|
return textNodes;
|
|
|
}
|
|
|
- let textNodes = getTextNodes(body);
|
|
|
+ const textNodes = getTextNodes(body);
|
|
|
if (textNodes.length === 0) return false;
|
|
|
return textNodes.every((node) => {
|
|
|
let el = node.parentElement;
|
|
|
@@ -928,12 +922,13 @@ export default {
|
|
|
return false;
|
|
|
});
|
|
|
},
|
|
|
+
|
|
|
/**
|
|
|
* 批量设置整体富文本格式
|
|
|
* @param {Array<{type: string, val: string}>} formats 格式数组,每个元素是一个对象,包含 type 和 val
|
|
|
*/
|
|
|
patchSetRichFormat(formats) {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (!editor) return;
|
|
|
editor.execCommand('SelectAll');
|
|
|
formats.forEach(({ type, val }) => {
|
|
|
@@ -944,11 +939,12 @@ export default {
|
|
|
|
|
|
if (this.isViewPinyin) {
|
|
|
this.$nextTick(() => {
|
|
|
- let styles = this.getFirstCharStyles();
|
|
|
+ const styles = this.getFirstCharStyles();
|
|
|
this.$emit('createParsedTextInfoPinyin', null, styles);
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
+
|
|
|
/**
|
|
|
* 设置整体富文本格式
|
|
|
* @param {string} type 格式名称
|
|
|
@@ -956,7 +952,7 @@ export default {
|
|
|
* @param {boolean} [isSetDefault=false] 是否设置为默认格式
|
|
|
*/
|
|
|
setRichFormat(type, val, isSetDefault = false) {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (!editor) return;
|
|
|
editor.execCommand('SelectAll');
|
|
|
this.handleEditorFormatter(editor, type, val);
|
|
|
@@ -1037,7 +1033,7 @@ export default {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (!editor) return;
|
|
|
|
|
|
// 获取编辑器内容区域
|
|
|
@@ -1197,8 +1193,9 @@ export default {
|
|
|
},
|
|
|
// 删除填空
|
|
|
deleteContent() {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
- let start = editor.selection.getStart();
|
|
|
+ const editor = this.editor;
|
|
|
+ if (!editor) return;
|
|
|
+ const start = editor.selection.getStart();
|
|
|
if (isNodeType(start, 'span')) {
|
|
|
let textContent = start.textContent;
|
|
|
let content = this.getRichSelectionContent();
|
|
|
@@ -1211,8 +1208,9 @@ export default {
|
|
|
},
|
|
|
// 设置填空
|
|
|
setContent() {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
- let start = editor.selection.getStart();
|
|
|
+ const editor = this.editor;
|
|
|
+ if (!editor) return;
|
|
|
+ const start = editor.selection.getStart();
|
|
|
let content = this.getRichSelectionContent();
|
|
|
if (isNodeType(start, 'span')) {
|
|
|
let textContent = start.textContent;
|
|
|
@@ -1226,8 +1224,9 @@ export default {
|
|
|
},
|
|
|
// 折叠选区
|
|
|
collapse() {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
- let rng = editor.selection.getRng();
|
|
|
+ const editor = this.editor;
|
|
|
+ if (!editor) return;
|
|
|
+ const rng = editor.selection.getRng();
|
|
|
if (!rng.collapsed) {
|
|
|
this.hideContentmenu();
|
|
|
editor.selection.collapse();
|
|
|
@@ -1310,7 +1309,8 @@ export default {
|
|
|
},
|
|
|
// 隐藏工具栏抽屉
|
|
|
hideToolbarDrawer() {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
+ if (!editor) return;
|
|
|
if (editor.queryCommandState('ToggleToolbarDrawer')) {
|
|
|
editor.execCommand('ToggleToolbarDrawer');
|
|
|
}
|
|
|
@@ -1328,7 +1328,9 @@ export default {
|
|
|
},
|
|
|
|
|
|
async mathConfirm(math) {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
+ if (this.mathRenderLock) return;
|
|
|
+ const editor = this.editor;
|
|
|
+ if (!editor) return;
|
|
|
let tmpId = getRandomNumber();
|
|
|
this.mathRenderLock = true;
|
|
|
try {
|
|
|
@@ -1348,7 +1350,8 @@ export default {
|
|
|
async renderMath(id) {
|
|
|
if (this.mathEleIsInit) return; // 如果公式已经渲染过,返回
|
|
|
if (window.MathJax) {
|
|
|
- let editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
+ if (!editor) return;
|
|
|
let eleMathArs = [];
|
|
|
if (id) {
|
|
|
// 插入的时候,会传递ID,执行单个渲染
|
|
|
@@ -1378,7 +1381,7 @@ export default {
|
|
|
|
|
|
// 选中文本打开弹窗
|
|
|
openExplanatoryNoteDialog() {
|
|
|
- const editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (!editor) {
|
|
|
console.error('编辑器未初始化');
|
|
|
return;
|
|
|
@@ -1515,7 +1518,7 @@ export default {
|
|
|
* @returns {object} 包含字体、字号、颜色、加粗、下划线、删除线等样式属性的对象
|
|
|
*/
|
|
|
getFirstCharStyles() {
|
|
|
- const editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (!editor) return {};
|
|
|
|
|
|
const firstTextNode = this.findFirstTextNode(editor.getBody());
|
|
|
@@ -1589,7 +1592,7 @@ export default {
|
|
|
* @returns {object} 包含字体、字号、颜色等body初始样式属性的对象
|
|
|
*/
|
|
|
getBodyInitialStyles() {
|
|
|
- const editor = tinymce.get(this.id);
|
|
|
+ const editor = this.editor;
|
|
|
if (!editor) return {};
|
|
|
|
|
|
const body = editor.getBody();
|