Przeglądaj źródła

feat: 公式编辑器着色

zhangjie 6 miesięcy temu
rodzic
commit
d8e31c7b66

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "lodash": "^4.17.15",
     "mathjax-full": "^3.2.2",
     "moment": "^2.29.1",
+    "prismjs": "^1.29.0",
     "spark-md5": "^3.0.2",
     "vue": "^2.6.12",
     "vue-awesome": "^4.1.0",

+ 54 - 7
src/assets/styles/formula-editor.scss

@@ -44,14 +44,61 @@
     }
   }
   .formula-input {
-    border-radius: 5px;
-    border: 1px solid #d1d1d1;
-    background: #fff;
-    height: 100%;
+    position: relative;
     overflow: hidden;
-    height: 150px;
-    &.is-focus {
-      border-color: #1886fe;
+    height: 182px;
+
+    &-body {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      padding: 10px;
+      border-radius: 5px;
+      border: 1px solid #d1d1d1;
+      background: transparent;
+      outline: none;
+      color: transparent;
+      // color: #a0a0a0;
+      z-index: 9;
+      font-size: 16px;
+      line-height: 20px;
+      resize: none;
+      box-sizing: border-box;
+      caret-color: #000;
+      font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
+
+      &:focus {
+        box-shadow: none;
+      }
+    }
+    &-view {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      padding: 10px;
+      border: 1px solid transparent;
+      z-index: 8;
+      overflow: auto;
+
+      &::-webkit-scrollbar-thumb {
+        background: transparent;
+      }
+
+      .language-latex {
+        background-color: transparent;
+        padding: 0;
+        margin: 0;
+        font-size: 16px;
+        line-height: 20px;
+      }
+      pre,
+      code {
+        display: inline-block;
+      }
     }
   }
 

+ 70 - 67
src/components/mathjax-editor/FormulaEditor.vue

@@ -13,10 +13,17 @@
   >
     <div class="formula-editor">
       <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">
         <h3>效果预览</h3>
         <div ref="previewBodyRef" class="preview-body"></div>
@@ -34,17 +41,12 @@
 import { debounce } from "lodash";
 import FormulaMenu from "./FormulaMenu.vue";
 import { renderLatexToSVG, svgToImage } from "./mathjax";
-import editorStyleCont from "./editorStyle";
-import { getInputContent } from "./input";
+import Prism from "./highlight";
 
 export default {
   name: "FormulaEditor",
   components: { FormulaMenu },
   props: {
-    placeholder: {
-      type: String,
-      default: "请输入...",
-    },
     content: {
       type: String,
       default: "",
@@ -54,10 +56,6 @@ export default {
     return {
       modalIsShow: false,
       inputLatex: "",
-      inited: false,
-      editorBodyContainer: null,
-      editorBodyFrame: null,
-      isFocus: false,
     };
   },
   methods: {
@@ -68,60 +66,40 @@ export default {
       this.modalIsShow = true;
     },
     openedHandle() {
-      if (this.inited) return;
-      this.initEditorBody();
+      this.registEvent();
       this.selectMenuHandle({ output: this.content });
     },
     closeHandle() {
-      this.inited = false;
-      this.$refs.editorBodyRef.innerHTML = "";
+      this.$refs.editorBodyRef.value = "";
+      this.$refs.editorViewRef.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() {
-      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
-    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) {
       e.preventDefault();
@@ -133,21 +111,46 @@ export default {
     },
     // menu
     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();
     }, 300),
     toParse() {
       try {
-        this.inputLatex = getInputContent(this.editorBodyContainer);
         const svg = renderLatexToSVG(this.inputLatex);
         this.$refs.previewBodyRef.innerHTML = svg;
       } catch (error) {

+ 0 - 43
src/components/mathjax-editor/editorStyle.js

@@ -1,43 +0,0 @@
-export default `
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-}
-html {
-  cursor: text;
-}
-body {
-font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
-  "Microsoft YaHei", Arial, sans-serif;
-}
-body[contenteditable="true"]:empty:not(:focus)::before {
-  content: attr(data-placeholder);
-  color: grey;
-}
-body img {
-  max-width: 100%;
-}
-body b {
-  font-weight: bold;
-}
-body i {
-  font-style: italic;
-}
-body u {
-  text-decoration: underline;
-}
-.editor-container{
-  border: none;
-  padding: 8px;
-  outline: none;
-  white-space: pre-wrap;
-  margin: 0;
-  min-height:100%;
-  font-size:18px;
-}
-.editor-container div {
-  min-height: 20px;
-  line-height: 20px;
-}
-`;

+ 8 - 0
src/components/mathjax-editor/highlight.js

@@ -0,0 +1,8 @@
+import Prism from "prismjs";
+import "prismjs/components/prism-latex";
+import "prismjs/themes/prism.css";
+// 内置主题
+// prismjs/themes/prism-[name].css
+// name: twilight tomorrow solarizedlight okaidia funky ark coy
+
+export default Prism;

+ 0 - 45
src/components/mathjax-editor/input.js

@@ -1,45 +0,0 @@
-export function getInputContent(editor) {
-  const contents = [];
-  const nodesSectionsCollector = toNodeSections(editor);
-  nodesSectionsCollector.forEach((section) => {
-    const sectionContent = [];
-    section.forEach((node) => {
-      sectionContent.push(node.textContent);
-    });
-    contents.push(sectionContent.join(""));
-  });
-  return contents.join("\n");
-}
-
-function toNodeSections(node) {
-  let sections = [];
-  let curSection = [];
-
-  const checkIsDiv = (elem) => {
-    return elem.nodeType == Node.ELEMENT_NODE && elem.nodeName === "DIV";
-  };
-
-  const parseNode = (node) => {
-    node.childNodes.forEach((elem) => {
-      if (checkIsDiv(elem) && curSection.length) {
-        sections.push(curSection);
-        curSection = [];
-      }
-
-      if (elem.childNodes && elem.childNodes.length) {
-        parseNode(elem);
-      } else {
-        curSection.push(elem);
-      }
-    });
-  };
-
-  parseNode(node);
-
-  if (curSection.length) {
-    sections.push(curSection);
-  }
-
-  // console.log(sections);
-  return sections;
-}

+ 4 - 19
src/modules/questions/views/GenPaper.vue

@@ -125,14 +125,11 @@
           width="50"
           align="center"
         ></el-table-column>
-        <el-table-column label="课程名称" width="180">
+        <el-table-column label="课程名称(代码)" width="180">
           <template slot-scope="scope">
-            <span>{{ scope.row.course.name }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="课程代码" width="80">
-          <template slot-scope="scope">
-            <span>{{ scope.row.course.code }}</span>
+            <span
+              >{{ scope.row.course.name }}({{ scope.row.course.code }})</span
+            >
           </template>
         </el-table-column>
         <el-table-column label="试卷名称" width="180">
@@ -173,18 +170,6 @@
           prop="creationTime"
         >
         </el-table-column>
-        <el-table-column label="修改人" width="120">
-          <template slot-scope="scope">
-            <span>{{ scope.row.lastModifyName }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column
-          label="修改时间"
-          width="153"
-          sortable
-          prop="updateTime"
-        >
-        </el-table-column>
         <el-table-column label="操作" width="180" fixed="right">
           <template slot-scope="scope">
             <div class="operate_left">

+ 5 - 0
yarn.lock

@@ -6077,6 +6077,11 @@ pretty-error@^4.0.0:
     lodash "^4.17.20"
     renderkid "^3.0.0"
 
+prismjs@^1.29.0:
+  version "1.29.0"
+  resolved "https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
+  integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
+
 process-nextick-args@~2.0.0:
   version "2.0.1"
   resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"