|
@@ -13,10 +13,17 @@
|
|
>
|
|
>
|
|
<div class="formula-editor">
|
|
<div class="formula-editor">
|
|
<FormulaMenu @select-menu="selectMenuHandle" />
|
|
<FormulaMenu @select-menu="selectMenuHandle" />
|
|
- <div
|
|
|
|
- ref="editorBodyRef"
|
|
|
|
- :class="['formula-input', { 'is-focus': isFocus }]"
|
|
|
|
- ></div>
|
|
|
|
|
|
+ <div class="formula-input">
|
|
|
|
+ <textarea
|
|
|
|
+ ref="editorBodyRef"
|
|
|
|
+ class="formula-input-body"
|
|
|
|
+ placeholder="请输入您的LaTeX表达式"
|
|
|
|
+ maxlength="9999"
|
|
|
|
+ spellcheck="false"
|
|
|
|
+ wrap="off"
|
|
|
|
+ ></textarea>
|
|
|
|
+ <div ref="editorViewRef" class="formula-input-view"></div>
|
|
|
|
+ </div>
|
|
<div class="formula-preview">
|
|
<div class="formula-preview">
|
|
<h3>效果预览</h3>
|
|
<h3>效果预览</h3>
|
|
<div ref="previewBodyRef" class="preview-body"></div>
|
|
<div ref="previewBodyRef" class="preview-body"></div>
|
|
@@ -34,17 +41,12 @@
|
|
import { debounce } from "lodash";
|
|
import { debounce } from "lodash";
|
|
import FormulaMenu from "./FormulaMenu.vue";
|
|
import FormulaMenu from "./FormulaMenu.vue";
|
|
import { renderLatexToSVG, svgToImage } from "./mathjax";
|
|
import { renderLatexToSVG, svgToImage } from "./mathjax";
|
|
-import editorStyleCont from "./editorStyle";
|
|
|
|
-import { getInputContent } from "./input";
|
|
|
|
|
|
+import Prism from "./highlight";
|
|
|
|
|
|
export default {
|
|
export default {
|
|
name: "FormulaEditor",
|
|
name: "FormulaEditor",
|
|
components: { FormulaMenu },
|
|
components: { FormulaMenu },
|
|
props: {
|
|
props: {
|
|
- placeholder: {
|
|
|
|
- type: String,
|
|
|
|
- default: "请输入...",
|
|
|
|
- },
|
|
|
|
content: {
|
|
content: {
|
|
type: String,
|
|
type: String,
|
|
default: "",
|
|
default: "",
|
|
@@ -54,10 +56,6 @@ export default {
|
|
return {
|
|
return {
|
|
modalIsShow: false,
|
|
modalIsShow: false,
|
|
inputLatex: "",
|
|
inputLatex: "",
|
|
- inited: false,
|
|
|
|
- editorBodyContainer: null,
|
|
|
|
- editorBodyFrame: null,
|
|
|
|
- isFocus: false,
|
|
|
|
};
|
|
};
|
|
},
|
|
},
|
|
methods: {
|
|
methods: {
|
|
@@ -68,60 +66,40 @@ export default {
|
|
this.modalIsShow = true;
|
|
this.modalIsShow = true;
|
|
},
|
|
},
|
|
openedHandle() {
|
|
openedHandle() {
|
|
- if (this.inited) return;
|
|
|
|
- this.initEditorBody();
|
|
|
|
|
|
+ this.registEvent();
|
|
this.selectMenuHandle({ output: this.content });
|
|
this.selectMenuHandle({ output: this.content });
|
|
},
|
|
},
|
|
closeHandle() {
|
|
closeHandle() {
|
|
- this.inited = false;
|
|
|
|
- this.$refs.editorBodyRef.innerHTML = "";
|
|
|
|
|
|
+ this.$refs.editorBodyRef.value = "";
|
|
|
|
+ this.$refs.editorViewRef.innerHTML = "";
|
|
this.$refs.previewBodyRef.innerHTML = "";
|
|
this.$refs.previewBodyRef.innerHTML = "";
|
|
- },
|
|
|
|
- initEditorBody() {
|
|
|
|
- const editorBodyFrame = document.createElement("iframe");
|
|
|
|
- editorBodyFrame.setAttribute("frameborder", 0);
|
|
|
|
- editorBodyFrame.style = `width:100%;height:100%;`;
|
|
|
|
- this.$refs.editorBodyRef.appendChild(editorBodyFrame);
|
|
|
|
- const editorStyles = document.createElement("style");
|
|
|
|
- editorStyles.innerHTML = editorStyleCont;
|
|
|
|
- editorBodyFrame.contentDocument.head.appendChild(editorStyles);
|
|
|
|
- const editorBodyContainer = document.createElement("div");
|
|
|
|
- editorBodyContainer.className = "editor-container";
|
|
|
|
- editorBodyFrame.contentDocument.body.appendChild(editorBodyContainer);
|
|
|
|
- editorBodyContainer.contentEditable = true;
|
|
|
|
- editorBodyContainer.dataset.placeholder = this.placeholder;
|
|
|
|
- this.editorBodyContainer = editorBodyContainer;
|
|
|
|
- this.editorBodyFrame = editorBodyFrame;
|
|
|
|
-
|
|
|
|
- this.inited = true;
|
|
|
|
-
|
|
|
|
- this.registEvent();
|
|
|
|
|
|
+ this.removeEvent();
|
|
},
|
|
},
|
|
registEvent() {
|
|
registEvent() {
|
|
- this.editorBodyContainer.addEventListener("input", this.inputChange);
|
|
|
|
- this.editorBodyContainer.addEventListener(
|
|
|
|
- "paste",
|
|
|
|
- this.editorBodyPasteEventHanle
|
|
|
|
- );
|
|
|
|
- this.editorBodyContainer.addEventListener(
|
|
|
|
- "focus",
|
|
|
|
- this.editorBodyFocusEventHanle
|
|
|
|
|
|
+ this.$refs.editorBodyRef.addEventListener("input", this.inputChange);
|
|
|
|
+ this.$refs.editorBodyRef.addEventListener(
|
|
|
|
+ "scroll",
|
|
|
|
+ this.editorBodyScrollEventHanle
|
|
);
|
|
);
|
|
- this.editorBodyContainer.addEventListener(
|
|
|
|
- "blur",
|
|
|
|
- this.editorBodyBlurEventHanle
|
|
|
|
|
|
+ },
|
|
|
|
+ removeEvent() {
|
|
|
|
+ this.$refs.editorBodyRef.removeEventListener("input", this.inputChange);
|
|
|
|
+ this.$refs.editorBodyRef.removeEventListener(
|
|
|
|
+ "scroll",
|
|
|
|
+ this.editorBodyScrollEventHanle
|
|
);
|
|
);
|
|
},
|
|
},
|
|
// event
|
|
// event
|
|
- editorBodyFocusEventHanle(e) {
|
|
|
|
- e.preventDefault();
|
|
|
|
- e.stopPropagation();
|
|
|
|
- this.isFocus = true;
|
|
|
|
- },
|
|
|
|
- editorBodyBlurEventHanle(e) {
|
|
|
|
- e.preventDefault();
|
|
|
|
- e.stopPropagation();
|
|
|
|
- this.isFocus = false;
|
|
|
|
|
|
+ editorBodyScrollEventHanle(e) {
|
|
|
|
+ // console.log({
|
|
|
|
+ // top: e.target.scrollTop,
|
|
|
|
+ // left: e.target.scrollLeft,
|
|
|
|
+ // });
|
|
|
|
+
|
|
|
|
+ this.$refs.editorViewRef.scrollTo({
|
|
|
|
+ top: e.target.scrollTop,
|
|
|
|
+ left: e.target.scrollLeft,
|
|
|
|
+ });
|
|
},
|
|
},
|
|
editorBodyPasteEventHanle(e) {
|
|
editorBodyPasteEventHanle(e) {
|
|
e.preventDefault();
|
|
e.preventDefault();
|
|
@@ -133,21 +111,46 @@ export default {
|
|
},
|
|
},
|
|
// menu
|
|
// menu
|
|
selectMenuHandle(data) {
|
|
selectMenuHandle(data) {
|
|
- const selection = this.editorBodyFrame.contentWindow.getSelection();
|
|
|
|
- if (!selection.focusNode) this.editorBodyContainer.focus();
|
|
|
|
- this.editorBodyFrame.contentDocument.execCommand(
|
|
|
|
- "insertText",
|
|
|
|
- false,
|
|
|
|
- data.output
|
|
|
|
|
|
+ const textArea = this.$refs.editorBodyRef;
|
|
|
|
+ // 插入纯文本内容
|
|
|
|
+ const start = textArea.selectionStart;
|
|
|
|
+ const end = textArea.selectionEnd;
|
|
|
|
+ const textBefore = textArea.value.substring(0, start);
|
|
|
|
+ const textAfter = textArea.value.substring(end);
|
|
|
|
+
|
|
|
|
+ textArea.value = textBefore + data.output + textAfter;
|
|
|
|
+
|
|
|
|
+ this.inputChange();
|
|
|
|
+ },
|
|
|
|
+ inputChange() {
|
|
|
|
+ this.highLightCode();
|
|
|
|
+ this.debounceParse();
|
|
|
|
+ },
|
|
|
|
+ highLightCode() {
|
|
|
|
+ this.inputLatex = this.$refs.editorBodyRef.value;
|
|
|
|
+ const inputs = this.inputLatex.split("\n").reverse();
|
|
|
|
+ const index = inputs.findIndex((item) => !!item);
|
|
|
|
+ let highLightInput = this.inputLatex;
|
|
|
|
+ let emptyLine = "";
|
|
|
|
+ if (index > 0) {
|
|
|
|
+ highLightInput = inputs.slice(index).reverse().join("\n");
|
|
|
|
+ for (let i = 0; i < index; i++) {
|
|
|
|
+ emptyLine += `\n <span> </span>`;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const html = Prism.highlight(
|
|
|
|
+ highLightInput,
|
|
|
|
+ Prism.languages.latex,
|
|
|
|
+ "latex"
|
|
);
|
|
);
|
|
- this.toParse();
|
|
|
|
|
|
+ this.$refs.editorViewRef.innerHTML = `<pre class="language-latex"><code>${html}${emptyLine}</code></pre>`;
|
|
},
|
|
},
|
|
- inputChange: debounce(function () {
|
|
|
|
|
|
+ debounceParse: debounce(function () {
|
|
this.toParse();
|
|
this.toParse();
|
|
}, 300),
|
|
}, 300),
|
|
toParse() {
|
|
toParse() {
|
|
try {
|
|
try {
|
|
- this.inputLatex = getInputContent(this.editorBodyContainer);
|
|
|
|
const svg = renderLatexToSVG(this.inputLatex);
|
|
const svg = renderLatexToSVG(this.inputLatex);
|
|
this.$refs.previewBodyRef.innerHTML = svg;
|
|
this.$refs.previewBodyRef.innerHTML = svg;
|
|
} catch (error) {
|
|
} catch (error) {
|