Markdown、Latex编辑小工具

Markdown、Latex编辑小工具

    • 文章说明
    • 主要代码
    • 效果展示
    • 源码下载

文章说明

本文主要为了书写Latex的书写风格,以及了解自己实现一个markdown类型的编辑器的过程;目前实现了当前的效果;书写文章进行记录,方便后续查阅

目前还未添加好markdown的代码高亮效果,等待后续自己实现一个高亮组件,然后添加到该demo中

文本相对复杂的几个小点为:
1、marked库的使用
2、katex库的使用
3、textarea元素的光标处插入内容的实现(本效果参考了文章:利用selection对象在textarea光标处插入指定文本)
4、滚动同步效果(目前实现的效果感觉不是很好,会有一些抖动的效果,感觉有点头晕,没有CSDN自带的编辑功能的这个同步效果的实现精细,不过我目前也没整明白它这里的实现原理是什么样的,如果有好的想法的同学欢迎提出在评论区中,如对demo有帮助,冰冰一号会进行采纳,同时添加到项目贡献者中)
Latex相关公式的查找(参考文章为:[markdown语法]公式篇–整理总结了常用的公式语法全)

实际关于markdown相关组件的使用,可以直接使用封装好的库,如v-md-editor,这种封装好的库使用相对简单,而且功能更加完善,参考链接:v-md-editor

主要代码

文件结构
在这里插入图片描述

markdown组件

<template>
  <div class="container">
    <textarea class="left" v-model="data.text" @scroll="scrollLeftSync"></textarea>
    <div class="right" v-html="renderedMarkdown" @scroll="scrollRightSync"></div>
  </div>
</template>

<script setup>
import {marked} from 'marked';
import {computed, onMounted, reactive} from "vue";

const data = reactive({
  text: "",
});

const renderedMarkdown = computed(() => {
  return marked.parse(data.text);
});

let left;
let right;

onMounted(() => {
  left = document.getElementsByClassName("left")[0];
  right = document.getElementsByClassName("right")[0];
  left.focus();
});

let leftScroll = true;
let rightScroll = true;

function scrollLeftSync() {
  if (!leftScroll) {
    return;
  }
  const percent = (left.scrollTop / (left.scrollHeight - left.clientHeight)).toFixed(2);
  rightScroll = false;
  right.scrollTo({
    top: (right.scrollHeight - right.clientHeight) * percent,
  });
  rightScroll = true;
}

function scrollRightSync() {
  if (!rightScroll) {
    return;
  }
  const percent = (right.scrollTop / (right.scrollHeight - right.clientHeight)).toFixed(2);
  leftScroll = false;
  left.scrollTo({
    top: (left.scrollHeight - left.clientHeight) * percent,
  });
  leftScroll = true;
}
</script>

<style lang="scss" scoped>
.container {
  width: 100%;
  height: 100%;

  .left {
    width: 50%;
    height: 100%;
    resize: none;
    border: none;
    outline: none;
    background: transparent;
    font-size: 1.4rem;
    padding: 1rem;
    display: block;
    overflow: auto;
    float: left;

    &::-webkit-scrollbar {
      width: 0.5rem;
      background-color: transparent;
      border-radius: 0.5rem;
      cursor: pointer;
    }

    &::-webkit-scrollbar-thumb {
      background-color: #bbbbbb;
      border-radius: 0.5rem;
      cursor: pointer;
    }
  }

  .right {
    width: 50%;
    height: 100%;
    background-color: #ffffff;
    font-size: 1.4rem;
    padding: 1rem;
    overflow: auto;
    float: left;

    &::-webkit-scrollbar {
      width: 0.5rem;
      height: 0.5rem;
      background-color: transparent;
      border-radius: 0.5rem;
    }

    &::-webkit-scrollbar-thumb {
      background-color: #bbbbbb;
      border-radius: 0.5rem;
    }
  }
}
</style>

Latex组件

<template>
  <div class="container">
    <div class="tool">
      <ul>
        <li @click="appendLine">换行</li>
        <li @click="superscript">上标</li>
        <li @click="subscript">下标</li>
        <li @click="vector">向量</li>
        <li @click="average">平均值</li>
        <li @click="fraction">分式</li>
        <li @click="dots">省略号</li>
        <li @click="sqrt">根式</li>
        <li @click="mul"></li>
        <li @click="div"></li>
        <li @click="ge">大于等于</li>
        <li @click="le">小于等于</li>
        <li @click="ne">不等于</li>
        <li @click="dx">导数</li>
        <li @click="infinity">无穷</li>
        <li @click="leftarrow">左箭头</li>
        <li @click="rightarrow">右箭头</li>
        <li @click="In">属于</li>
        <li @click="notIn">不属于</li>
        <li @click="overbrace">上大括号</li>
        <li @click="underbrace">下大括号</li>
        <li @click="sin">sin</li>
        <li @click="cos">cos</li>
        <li @click="tan">tan</li>
        <li @click="log">log</li>
        <li @click="lg">lg</li>
        <li @click="ln">ln</li>
        <li @click="equationSet">方程组</li>
        <li @click="calculationProcess">计算过程</li>
      </ul>
    </div>
    <div class="content">
      <textarea class="left" v-model="data.text" @scroll="scrollLeftSync"></textarea>
      <div class="right" v-html="data.parseText" @scroll="scrollRightSync"></div>
    </div>
  </div>
</template>

<script setup>
import {onMounted, reactive, watch} from "vue";
import katex from 'katex'

const data = reactive({
  text: "",
  parseText: "",
});

const renderOption = {
  delimiters: [
    {left: '$$', right: '$$', display: true},
    {left: '$', right: '$', display: false},
    {left: '\\(', right: '\\)', display: false},
    {left: '\\[', right: '\\]', display: true}
  ],
  throwOnError: false
}

watch(() => data.text, () => {
  data.parseText = katex.renderToString(data.text, renderOption);
});

let left;
let right;

onMounted(() => {
  left = document.getElementsByClassName("left")[0];
  right = document.getElementsByClassName("right")[0];
  left.focus();
});

let leftScroll = true;
let rightScroll = true;

function scrollLeftSync() {
  if (!leftScroll) {
    return;
  }
  const percent = (left.scrollTop / (left.scrollHeight - left.clientHeight)).toFixed(2);
  rightScroll = false;
  right.scrollTo({
    top: (right.scrollHeight - right.clientHeight) * percent,
  });
  rightScroll = true;
}

function scrollRightSync() {
  if (!rightScroll) {
    return;
  }
  const percent = (right.scrollTop / (right.scrollHeight - right.clientHeight)).toFixed(2);
  leftScroll = false;
  left.scrollTo({
    top: (left.scrollHeight - left.clientHeight) * percent,
  });
  leftScroll = true;
}

function insertAtCursor(f, value, callback) {
  let field = f
  let newValue
  if (field.selectionStart || field.selectionStart === 0) {
    const startPos = field.selectionStart
    const endPos = field.selectionEnd
    const restoreTop = field.scrollTop
    newValue = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length)
    if (restoreTop > 0) {
      field.scrollTop = restoreTop
    }
    field.focus()
    setTimeout(() => {
      field.selectionStart = startPos + value.length
      field.selectionEnd = startPos + value.length
    }, 0)
  } else {
    newValue = field.value + value
    field.focus()
  }
  callback(newValue)
}

function addContent(text) {
  insertAtCursor(left, text, (newValue) => {
    data.text = newValue;
  });
}

function appendLine() {
  addContent("\n\\\\\\\n");
}

function superscript() {
  addContent(" x^y ");
}

function subscript() {
  addContent(" x_y ");
}

function vector() {
  addContent(" \\vec{a} ");
}

function average() {
  addContent(" \\overline{a} ");
}

function fraction() {
  addContent(" \\frac{1}{2} ");
}

function dots() {
  addContent(" \\cdots ");
}

function sqrt() {
  addContent(" \\sqrt[2]{x+y} ");
}

function mul() {
  addContent(" \\times ");
}

function div() {
  addContent(" \\div ");
}

function ge() {
  addContent(" \\ge ");
}

function le() {
  addContent(" \\le ");
}

function ne() {
  addContent(" \\ne ");
}

function dx() {
  addContent(" x{\\prime} ");
}

function infinity() {
  addContent(" \\infty ");
}

function leftarrow() {
  addContent(" \\leftarrow ");
}

function rightarrow() {
  addContent(" \\rightarrow ");
}

function In() {
  addContent(" \\in ");
}

function notIn() {
  addContent(" \\notin ");
}

function overbrace() {
  addContent(" \\overbrace{1+2+\\cdots+100} ");
}

function underbrace() {
  addContent(" \\underbrace{1+2+\\cdots+100} ");
}

function sin() {
  addContent(" \\sin 30^\\circ ");
}

function cos() {
  addContent(" \\cos 30^\\circ ");
}

function tan() {
  addContent(" \\tan 30^\\circ ");
}

function log() {
  addContent(" \\log_2 8 ");
}

function lg() {
  addContent(" \\lg 10 ");
}

function ln() {
  addContent(" \\ln 2 ");
}

function equationSet() {
  addContent("\nf(n)= \\begin{cases}\n" +
      "n/2, & \\text {if $n$ is even} \\\\\n" +
      "3n+1, & \\text{if $n$ is odd}\n" +
      "\\end{cases}\n");
}

function calculationProcess() {
  addContent("\n\\begin{aligned}\n" +
      "    f(x)\n" +
      "    &=x^3+3x^2+3x+1\\\\\n" +
      "    &=(x+1)^3\\\\\n" +
      "\\end{aligned}\n");
}
</script>

<style lang="scss" scoped>
.container {
  width: 100%;
  height: 100%;

  .tool {
    width: 100%;
    height: 6rem;
    border-bottom: 0.1rem solid #e0e1e2;
    background-color: #ffffff;

    ul {
      list-style: none;
      height: 3rem;
      line-height: 3rem;
      user-select: none;

      li {
        float: left;
        padding: 0 0.5rem;
        height: 3rem;
        text-align: center;
        color: #409eff;

        &:hover {
          cursor: pointer;
          color: #79bbff;
        }
      }
    }
  }

  .content {
    width: 100%;
    height: calc(100% - 6rem);

    .left {
      width: 50%;
      height: 100%;
      resize: none;
      border: none;
      outline: none;
      background: transparent;
      font-size: 1.4rem;
      padding: 1rem;
      display: block;
      float: left;

      &::-webkit-scrollbar {
        width: 0.5rem;
        background-color: transparent;
        border-radius: 0.5rem;
      }

      &::-webkit-scrollbar-thumb {
        background-color: #bbbbbb;
        border-radius: 0.5rem;
      }
    }

    .right {
      width: 50%;
      height: 100%;
      background-color: #ffffff;
      font-size: 1.4rem;
      padding: 1rem;
      overflow: auto;
      float: left;

      &::-webkit-scrollbar {
        width: 0.5rem;
        height: 0.5rem;
        background-color: transparent;
        border-radius: 0.5rem;
      }

      &::-webkit-scrollbar-thumb {
        background-color: #bbbbbb;
        border-radius: 0.5rem;
      }
    }
  }
}
</style>

效果展示

markdown编辑展示
在这里插入图片描述

Latex编辑展示
在这里插入图片描述

源码下载

冰冰markdown小工具

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/759634.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

库存管理系统基于spingboot vue的前后端分离仓库库存管理系统java项目java课程设计java毕业设计

文章目录 库存管理系统一、项目演示二、项目介绍三、部分功能截图四、部分代码展示五、底部获取项目源码&#xff08;9.9&#xffe5;带走&#xff09; 库存管理系统 一、项目演示 库存管理系统 二、项目介绍 基于spingboot和vue前后端分离的库存管理系统 功能模块&#xff…

鸿蒙开发设备管理:【@ohos.multimodalInput.inputEventClient (注入按键)】

注入按键 InputEventClient模块提供了注入按键能力。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。本模块接口均为系统接口&#xff0c;三方应用不支持调用。 导入模块 import inputEventCli…

1、音视频解封装流程---解复用

对于一个视频文件(mp4格式/flv格式)&#xff0c;audio_pkt或者video_pkt是其最基本的数据单元&#xff0c;即视频文件是由独立的视频编码包或者音频编码包组成的。 解复用就是从视频文件中把视频包/音频包单独读取出来保存成独立文件&#xff0c;那么如何得知packet是视频包还是…

账号和权限的管理1

文章目录 修改用户账号的属性usermod格式常用选项 用户账号的初始化配置文件文件来源主要的用户初始配置文件 组账号文件添加组账号groupadd格式常用选项其他选项 删除组账号groupdel格式 查询账号信息groups格式 id格式 finger格式 W、who、users格式 文件/目录的权限和归属访…

Linux实用命令练习

目录 一、常用命令 二、系统命令 三、用户和组 四、权限 五、文件相关命令 六、查找 七、正则表达式 八、输入输出重定向 九、进程控制 十、其他命令 1、远程文件复制&#xff1a;scp 2、locate查找 3、which命令 4、设置或显示环境变量&#xff1a;export 5、修…

Free Pascal语言基础学习:定义变量、数据类型、循环语句、case语句、条件判断、with语句、运算符

Pascal是一种结构化编程语言&#xff0c;而Free Pascal作为其现代编译器&#xff0c;不仅支持跨多种操作系统和处理器架构&#xff0c;还提供了高效的内存使用和函数重载等先进功能。Free Pascal继承了Pascal语言的核心特性&#xff0c;同时进行了扩展和优化&#xff0c;使其成…

最流行的文件同步软件

PanguFlow是一款免费的文件同步软件&#xff0c;他支持文件的全量同步、支持文件的增量同步、支持文件的实时备份&#xff0c;支持双向同步&#xff0c;支持三向同步甚至多向同步&#xff0c;支持无人值守运行。 PanguFlow数据同步软件下载地址https://pan.baidu.com/s/1GLjFR…

python实现网页自动化(自动登录需要验证的网页)

引言: python作为实现网页自动化的一个重要工具,其强大的各种封装的库使得程序运行更加简洁,只需要下载相应的库,然后调用库中的函数就可以简便的实现我们想要的网页相关操作。 正文: 我的前几篇文章写了关于初学爬虫中比较容易上手的功能,例如爬取静态网页的数据、动…

【Elasticsearch】linux使用supervisor常驻Elasticsearch,centos6.10安装 supervisor

背景&#xff1a; linux服务器&#xff0c;CentOS 6操作系统&#xff0c;默认版本python2.6.6&#xff0c;避免安装过多的依赖不升级python 在网上查的资料python2.6.6兼容supervisor版本 3.1.3 安装supervisor 手动在python官网下载supervisor&#xff0c;并上传到服务器 下…

解锁横向招聘:创新您的人才搜索

技能差距仍然是面试官面临的问题之一。在这些空缺职位中&#xff0c;很难找到合适的技能候选人&#xff0c;特别是高级职位或以上职位。另一方面&#xff0c;申请人也发现很难找到一份适合自己的工作&#xff0c;因为他们抱怨工作要求太窄或太具体。在具有挑战性的职位招聘环境…

扛鼎中国AI搜索,天工凭什么?

人类的创作不会没有瓶颈&#xff0c;但AI的热度可不会消停。 大模型之战依旧精彩&#xff0c;OpenAI选择在Google前一天举行发布会&#xff0c;两家AI企业之间的拉扯赚足了热度。 反观国内&#xff0c;百模大战激发了大家对于科技变革的热切期盼&#xff0c;而如今行业已逐渐…

生成独立的zedboard+ad9361起始项目

文件分享 链接&#xff1a;https://pan.baidu.com/s/17wB_9xVWjO7HhxNvmmZyuA 提取码&#xff1a;94zz 首先下载HDL和NO-OS项目 git clone --recursive https://github.com/analogdevicesinc/hdl git clone --recursive https://github.com/analogdevicesinc/no-OS下载…

用人工智能大模型预报气象,中国气象局示范计划公开征集火热报名中

近日&#xff0c;中国气象局发布了人工智能气象预报大模型示范计划&#xff08;以下简称“示范计划”&#xff09;&#xff0c;推进气象大模型标准规范和有序发展&#xff0c;引导解决预报业务实际难题&#xff0c;促进人工智能气象预报大模型业务的应用转化、准入&#xff0c;…

【Linux】初识操作系统

一、冯•诺依曼体系结构 在学习操作系统之前&#xff0c;我们先来认识一下冯•诺依曼体系结构&#xff0c;我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 截至目前&#xff0c;我们所认识的计算机&am…

Navicat上新啦

前言 Navicat&#xff0c;在数据库界&#xff0c;几乎是一个神奇的存在&#xff0c;似乎统治了数据库开发工具的“一片天”。且看下图&#xff1a; 红的蓝的绿的橙的…&#xff0c;可以说&#xff0c;留给它的color不多了。 那么商业BI到服务监控、从云托管到云协作&#xff…

VUE3-Elementplus-form表单-笔记

1. 结构相关 el-row表示一行&#xff0c;一行分成24份 el-col表示列 (1) :span"12" 代表在一行中&#xff0c;占12份 (50%) (2) :span"6" 表示在一行中&#xff0c;占6份 (25%) (3) :offset"3" 代表在一行中&#xff0c;左侧margin份数 el…

5G NR PUSCH物理层过程

物理层过程 加扰 假设要在单个码字q上传输的bit块为 b ( q ) ( 0 ) , . . . , b ( q ) ( M b i t ( q ) − 1 ) b^{(q)}(0),...,b^{(q)}(M_{bit}^{(q)} - 1) b(q)(0),...,b(q)(Mbit(q)​−1) &#xff0c;其中 M b i t ( q ) M_{bit}^{(q)} Mbit(q)​是总比特数&#xff0c;加…

《昇思25天学习打卡营第16天 | 昇思MindSpore基于MobileNetv2的垃圾分类》

16天 本节学习了垃圾分类代码开发的方法。通过读取本地图像数据作为输入&#xff0c;对图像中的垃圾物体进行检测&#xff0c;并且将检测结果图片保存到文件中。 MobileNet网络是由Google团队于2017年提出的专注于移动端、嵌入式或IoT设备的轻量级CNN网络&#xff0c;相比于传…

2024.6.30周报

目录 摘要 ABSTRACT 一、文献阅读 一、题目 二、摘要 三、模型架构 四、文章解读 一、Introduction 二、创新点 三、RBM 四、贪心算法 五、实验 六、结论 二、代码复现 总结 摘要 本周我阅读了一篇题目为Generative Pre-Trained Physics-Informed Neural Netwo…

树莓派4B学习笔记16:Python引用自定义模块_简单模块化

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Python 版本3.7.3&#xff1a; 今日学习&#xff1a;Python引用自定义模块 文章提供测试…