nhn / tui.editor

🍞📝 Markdown WYSIWYG Editor. GFM Standard + Chart & UML Extensible.

Home Page:http://ui.toast.com/tui-editor

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Optionally Support rendered inline math using $$.

a1473838623 opened this issue · comments

Version

Latest

Development Environment

OS

Current Behavior

$$ line math not working. we can using code block to custom latex render, but code block is not a very popular way to render latex which make it difficult to change editor.

$$latex
\sin x
$$

And my current solution is render from text, which makes my solution become very limited:
My solution support Katex、Latex math formula using $$ (some has been tested, not every formula), and this solution is limited because you need press "format change" button for the first time(or you can just notice not to use special formula like { 、}、\、*、, and so on).

Despite it is not perfect, I can use this solution to cover my old Latex formula. And you can judge if to use this solution or not by checking these pictures.

Really hope ui.editor can support Latex someday.

tui.editor:
image

other md editor:
image

import { getLocale } from '@umijs/max';
import { Button } from 'antd';
import { useRef} from 'react';
import { Editor } from '@toast-ui/react-editor';
//@ts-ignore
import katex from 'katex';
import { MdNode } from '@toast-ui/editor';

const EditBlog = () => {
  const editorRef = useRef(null);
  const lang = getLocale().toString();

  function renderMathOnLeft(str: string) {
    const html = katex.renderToString(str, {
      throwOnError: false,
      displayMode: false,
      output: "mathml",
      globalGroup: true,
      fleqn: true,
    });
    return html;
  }

  // function renderMathOnCenter(str: string) {
  //   const html = katex.renderToString(str, {
  //     throwOnError: false,
  //     displayMode: false,
  //     output: "mathml",
  //     globalGroup: true,
  //     // this config can render math on center
  //     fleqn: false
  //   });
  //   return html;
  // }

  let latexPart: string = "";
  let ifEntering: boolean = false;
  let ifEven = true;
  let lastUncloseNode: MdNode | null = null;
  function getInlineMath(node: MdNode) {
    let noNeedNewLine = false;
    let str = node?.literal || "";
    let prevIdx = 0;
    let nextIdx = -1;
    let ifLastNode = (node?.next === null);
    let count = 0;
    if (node !== null && node === node?.parent?.firstChild) {
      let n: MdNode | null = node;
      while (n !== null) {
        for (let i = 0; i < (n?.literal?.length ?? 0); i++) {
          if (n?.literal?.[i] === "$") {
            count++;
          }
        }
        if (count % 2 === 1) {
          lastUncloseNode = n;
        } else {
          lastUncloseNode = null;
        }
        n = n?.next;
      }
      ifEven = (count % 2 === 0);
    }
    while (true) {
      prevIdx = str.indexOf("$", prevIdx);
      nextIdx = str.indexOf("$", prevIdx + 1);
      if (ifLastNode && !ifEntering && (prevIdx === -1 || nextIdx === -1)) {
        ifEntering = false;
        latexPart = '';
        break;
      }
      if (ifLastNode && ifEntering && prevIdx === -1) {
        ifEntering = false;
        latexPart = '';
        break;
      }
      if (ifEntering && prevIdx !== -1) {
        latexPart += ' ' + str.slice(0, prevIdx);
        str = str.replace(str.slice(0, prevIdx + 1), renderMathOnLeft(latexPart));
        latexPart = '';
        ifEntering = false;
        prevIdx += 1;
        continue;
      }
      if (!ifEntering && prevIdx !== -1 && nextIdx !== -1) {
        str = str.replace(str.slice(prevIdx, nextIdx + 1), renderMathOnLeft(str.slice(prevIdx + 1, nextIdx)));
        prevIdx = nextIdx + 1;
        continue;
      }
      if (!ifLastNode && !ifEntering && prevIdx === -1) {
        break;
      }
      if (!ifLastNode && !ifEntering && prevIdx !== -1 && nextIdx === -1) {
        ifEntering = true;
        latexPart = str.slice(prevIdx + 1) + ' ';
        if (ifEven || !Object.is(node, lastUncloseNode)) {
          str = str.replace(str.slice(prevIdx), '');
          noNeedNewLine = true;
        }
        break;
      }
      if (!ifLastNode && ifEntering && prevIdx === -1) {
        latexPart += ' ' + str.slice(0) + ' ';
        if (ifEven || !Object.is(node, lastUncloseNode)) {
          str = '';
          noNeedNewLine = true;
        }
        break;
      }
    }
    if (noNeedNewLine) {
      node.literal = "$";
    }
    return str;
  }

  const latexFilter = (str: string) => {
    return str?.replaceAll("\\\\", " \\newline ").replaceAll("\\{", " \\lbrace ").replaceAll("\\}", " \\rbrace ").replaceAll("*", " \\ast ").replaceAll("\\,", " \\thinspace ").replaceAll("_", " _ ").replaceAll("[", " \\lbrack ").replaceAll("]", " \\rbrack ");
  }

  const formatLatex = () => {
    // @ts-ignore
    const ed = editorRef?.current?.getInstance();
    let md = ed?.getMarkdown()?.replaceAll("$$=$$", "=");
    let mdCopy: string = md;
    let prevIdx = 0;
    let nextIdx = -1;
    while (true) {
      prevIdx = md.indexOf("$", prevIdx);
      nextIdx = md.indexOf("$", prevIdx + 1);
      if (prevIdx === -1) {
        break;
      } else if (nextIdx !== -1) {
        let partMd = md?.slice(prevIdx, nextIdx + 1);
        let tempMd = "$" + md?.slice(prevIdx + 1, nextIdx)?.trim() + "$";
        let partMdReplace = latexFilter(tempMd);
        mdCopy = mdCopy?.replace(partMd, partMdReplace);
        prevIdx = nextIdx + 1;
      } else {
        let partMd = md?.slice(prevIdx);
        let tempMd: string = "$" + md?.slice(prevIdx + 1)?.trim();
        let partMdReplace = latexFilter(tempMd);
        mdCopy = mdCopy?.replace(partMd, partMdReplace);
        break;
      }
    }
    ed?.setMarkdown(mdCopy);
  }
  return (
    <>
      <Button onClick={formatLatex}>format latex once(only once!!!)</Button>
      <Editor
        initialValue={`$sin
        x$`}
        previewStyle="vertical"
        height="600px"
        initialEditType="markdown"
        useCommandShortcut={true}
        usageStatistics={false}
        language={lang}
        hideModeSwitch={true}
        customHTMLRenderer={{
          text(node) {
            const str = getInlineMath(node);
            const isNodeHTML = (str?.indexOf("<span class=\"katex\">") !== -1);
            return {
              type: isNodeHTML ? 'html' : 'text',
              content: str
            }
          },
          softbreak(node: MdNode) {
            const isPrevNodeHTML = node.prev && node.prev.type === 'htmlInline';
            const isPrevBR = isPrevNodeHTML && /<br ?\/?>/.test(node.prev!.literal!);
            let str = undefined;
            if (node?.prev !== null) {
              str = node?.prev?.literal;
            }
            const content = (str === "$" ? "" : (isPrevBR ? '\n' : '<br>\n'));
            return { type: isPrevBR ? 'text' : 'html', content };
          },
        }}
        ref={editorRef}
      />
    </>
  );
};

export default EditBlog;

Expected Behavior

other md editor:
image

Please just make Latex an option, that would not confuse those people who don't use Latex.