跳转至

插入数学公式

数学公式编辑器

html模拟效果

KaTexEditDialog.html

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>KaTeX公式编辑器</title>
  <link rel="stylesheet" href="../../../main/lib/Katex/katex.css">
  <script src="../../../main/lib/Katex/katex.js"></script>
  <style>
    #textInput {width: 620px;height: 100px;overflow-y: auto;margin-left: 20px;margin-top: 10px;flex-direction: column}
    #katex-preview {width: 600px;height: 150px;display:flex;justify-content:center;align-items: center}
    .btn-style {width:650px;margin-top: 10px; display:flex; justify-content:center;align-items: center;gap: 150px}
  </style>
</head>
<body>
  <div id="katex-container">
    <div style="flex-direction: row">
      <div><label style="width: 10px">预览:</label></div>
      <div id="katex-preview" ></div>
    </div>
    <div style="width:650px;height: 2px; margin-top: 10px; color: black; background-color: black"></div>
    <div style="margin-top: 10px">
      <div><label style="width: 10px; margin-top: 10px">公式编辑:</label></div>
      <div><textarea id="textInput" class="text-input"></textarea></div>
    </div>
  </div>
  <div class="btn-style">
    <button id="insert-math-line" onclick="sendInsertMathLineText()">插入行内公式</button>
    <button id="insert-math-block" onclick="sendInsertMathBlockText()">插入公式块</button>
    <button id="cancel-insert-math" onclick="sendCancelInsertMathText()">取消编辑</button>
  </div>
  <script>
  // const { ipcRenderer } = require('electron')
  let latex = ''
  document.addEventListener('DOMContentLoaded', () => {
    // 初始显示 c = \sqrt{a^2 + b^2}
    const latexInit = '傅里叶级数公式 : x(t) = \\frac{a_0}{2} + \\sum_{n=1}^{\\infty} \\left( a_n \\cos\\left(\\frac{2\\pi nt}{T}\\right) + b_n \\sin\\left(\\frac{2\\pi nt}{T}\\right) \\right)'
    document.getElementById('katex-preview').innerHTML = katex.renderToString(latexInit)
    document.getElementById('textInput').textContent = latexInit
  })
  // 处理textInput的input事件,获取内容,渲染后进行显示
  function updateTextInput(event) {
    const inputText = event.target.value
    let html = ''
    try {
      html = katex.renderToString(inputText)
    } catch (error) {
      html = latex
    }
    document.getElementById("katex-preview").innerHTML = html
    latex = event.target.value
  }
  // 监控textInput的input事件
  document.getElementById('textInput').addEventListener('input', updateTextInput);
  // 这里是JavaScript函数,例如:
  function sendInsertMathLineText() {
    ipcRenderer.send('user-insert-math-line-text', '$' + latex + '$')
  }
  function sendInsertMathBlockText() {
    ipcRenderer.send('user-insert-math-block-text', '$$' + latex + '$$')
  }
  function sendCancelInsertMathText() {
    ipcRenderer.send('user-insert-math-text-cancel')
  }
  </script>
</body>
</html>

数学公式插入对话框

因为源html文件是有加入<script src="../../../main/lib/Katex/katex.js"></script>引入katex脚本的,但是在ts中直接loadUrl会出错,所以在写ts文件的时候,直接引入这部分去掉了,换成了通过IPC通信

渲染进程监控到编辑区域内容变化后,发送同步消息给主进程,主进程进行katex渲染,然后返回给渲染进程,由渲染进程更新预览区域数据。

实现的时候,发现渲染结果里面包含katex-mathmlkatex-html两个标签,俩都被显示出来了,通过css标签样式设置,将其中一个给隐藏掉了。

KaTexEditDialog.ts

TypeScript
import { BrowserWindow, ipcMain } from 'electron'
import { katexRenderToString } from '../../utils/MarkdownContentRender'
let customMathTextDialog: Electron.BrowserWindow | null
export function showMathTextDialog(mainWindow: Electron.BrowserWindow) {
  createMathTextDialog(mainWindow)
}
const latexInit =
  '傅里叶级数公式:x(t) = \\frac{a_0}{2} + \\sum_{n=1}^{\\infty} \\left( a_n \\cos\\left(\\frac{2\\pi nt}{T}\\right) + b_n \\sin\\left(\\frac{2\\pi nt}{T}\\right) \\right)'
// 创建一个自定义对话框的函数
function createMathTextDialog(mainWindow: Electron.BrowserWindow) {
  customMathTextDialog = new BrowserWindow({
    width: 1280,
    height: 550,
    minimizable: false,
    maximizable: false,
    title: '文字样式选择',
    autoHideMenuBar: true,
    webPreferences: {
      nodeIntegration: true, // 允许在渲染器进程中使用 Node.js 功能(注意:出于安全考虑,新版本 Electron 默认禁用)
      contextIsolation: false, // 禁用上下文隔离(同样出于安全考虑,新版本 Electron 默认启用)
      sandbox: false
    }
  })
  if (!customMathTextDialog) {
    return
  }
  customMathTextDialog.setMenu(null)
  // 加载一个 HTML 文件作为对话框的内容
  customMathTextDialog.loadURL(
    `data:text/html;charset=utf-8,${encodeURIComponent(mdMathTextDialogHtmlContext)}`
  )
  // 当窗口关闭时,清除引用
  customMathTextDialog.on('closed', () => {
    ipcMain.removeListener('user-insert-math-line-text', processMathLineTextInsert)
    ipcMain.removeListener('user-insert-math-block-text', processMathBlockTextInsert)
    ipcMain.removeListener('user-insert-math-text-cancel', () => {})
    customMathTextDialog = null
  })
  // 显示窗口
  customMathTextDialog.show()
  function exitCustomFontDialog() {
    if (customMathTextDialog) {
      ipcMain.removeListener('user-insert-math-line-text', processMathLineTextInsert)
      ipcMain.removeListener('user-insert-math-block-text', processMathBlockTextInsert)
      ipcMain.removeListener('user-insert-math-text-cancel', () => {})
      customMathTextDialog.close()
      customMathTextDialog = null
    }
  }
  function processMathLineTextInsert(_, mathText) {
    mainWindow.webContents.send('monaco-insert-text-block-templates', mathText + '\n')
    exitCustomFontDialog()
  }
  function processMathBlockTextInsert(_, mathText) {
    mainWindow.webContents.send('monaco-insert-text-block-templates', mathText + '\n')
    exitCustomFontDialog()
  }
  ipcMain.on('user-insert-math-line-text', processMathLineTextInsert)
  ipcMain.on('user-insert-math-block-text', processMathBlockTextInsert)
  ipcMain.on('user-insert-math-text-cancel', () => {
    exitCustomFontDialog()
  })
  ipcMain.on('sync-katex-render-message', (event, arg) => {
    if (arg == '') {
      event.returnValue = katexRenderToString(latexInit)
    } else {
      event.returnValue = katexRenderToString(arg)
    }
  })
}

预览区支持公式预览

markdown-it本身不支持ketex渲染,而我用的是election-vite框架,ts语言,本身又没法直接import对应的katex作为markdown-it插件使用,所以想了个比较笨的办法。

额外增加了一个专门进行katex渲染处理的ts文件,在vue组件中import后使用。

组件直接返回渲染结果,然后再放在markdown-it中进行render处理。

TypeScript
1
2
3
function updateMarkdown() {
  renderedMarkdownContent.value = md.render(katexRenderMathInText(props.code))
}
KaTexEditDialogUtils.ts

TypeScript
function renderMathInText(text: string, regex: RegExp, isBlock: boolean): string {
  // 正则表达式匹配以 $ 开头和结尾的文本(简单版本,不处理转义字符或嵌套)
  let result = text
  let match: RegExpExecArray | null = null

  // 使用全局搜索来查找所有匹配项
  while ((match = regex.exec(text)) !== null) {
    // 获取匹配到的 LaTeX 字符串(去掉 $ 符号)
    const latex = match[1]
    // 使用 KaTeX 渲染 LaTeX 字符串为 HTML
    let html = ''
    try {
      html = katex.renderToString(latex)
    } catch (error) {
      html = latex
    }
    if (isBlock) {
      html = '<div style="text-align: center;"><p>' + html + '</p></div>'
    }
    // 替换原始文本中的 $latex$ 为渲染后的 HTML
    // 注意:这里我们假设文本中不含有会破坏 HTML 的特殊字符
    result = result.replace(match[0], html)
  }
  return result
}
function renderMathLineInText(text: string): string {
  // 正则表达式匹配以 $ 开头和结尾的文本(简单版本,不处理转义字符或嵌套)
  const regex = /\$([^$]+)\$/g

  return renderMathInText(text, regex, false)
}
function renderMathBlockInText(text: string): string {
  // 正则表达式匹配以 $ 开头和结尾的文本(简单版本,不处理转义字符或嵌套)
  const regex = /\$\$([^$]+)\$\$/g

  return renderMathInText(text, regex, true)
}
export function katexRenderToString(text: string): string {
  let html = ''
  try {
    html = katex.renderToString(text)
  } catch (error) {
    html = text
  }
  return html
}
export function katexRenderMathInText(text: string): string {
  // 正则表达式匹配以 $ 开头和结尾的文本(简单版本,不处理转义字符或嵌套)
  const result = renderMathBlockInText(text)
  return renderMathLineInText(result)
}