Markdown、Latex编辑小工具

news2025/1/17 15:41:52

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.coloradmin.cn/o/1881418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙 HarmonyOs 动画效果 快速入门

一、理论 1.1 animation属性 名称参数类型必填描述durationnumber否设置动画时长&#xff0c;默认值&#xff1a;1000&#xff0c;单位&#xff1a;毫秒temponumber否动画播放速度。数值越大&#xff0c;速度越快&#xff0c;默认为1curvestring | Curve否 设置动画曲线。 默…

在node环境使用MySQL

什么是Sequelize? Sequelize是一个基于Promise的NodeJS ORM模块 什么是ORM? ORM(Object-Relational-Mapping)是对象关系映射 对象关系映射可以把JS中的类和对象&#xff0c;和数据库中的表和数据进行关系映射。映射之后我们就可以直接通过类和对象来操作数据表和数据了, 就…

昇思25天学习打卡营第13天|MindNLP ChatGLM-6B StreamChat

学AI还能赢奖品&#xff1f;每天30分钟&#xff0c;25天打通AI任督二脉 (qq.com) MindNLP ChatGLM-6B StreamChat 本案例基于MindNLP和ChatGLM-6B实现一个聊天应用。 1 环境配置 %%capture captured_output # 实验环境已经预装了mindspore2.2.14&#xff0c;如需更换mindspo…

len()函数——计算字符串长度或元素个数

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 len()函数的主要功能是获取一个&#xff08;字符、列表、元组等&#xff09;可迭代对象的长度或项目个数。其语法格式如下&#xff1a; l…

精密空气加热器负载组

小型便携式 &#xff1a;精密空气加热器&#xff08;负载组&#xff09;能够对数据中心热通道/冷通道冷却系统进行全面测试。EAK 是一款 19 英寸机架式设备&#xff08;10U 高&#xff09;&#xff0c;可轻松安装到各种标准服务器机架中。通过集成可调节的热量水平&#xff08;…

【Android面试八股文】性能优化相关面试题: 什么是内存抖动?什么是内存泄漏?

文章目录 一、什么是内存抖动?内存抖动的问题卡顿OOM(Out Of Memory)二、什么是内存泄漏(Memory Leak)?引用计数法可达性分析法一、什么是内存抖动? 在Java中,每创建一个对象,就会申请一块内存,存储对象信息; 每分配一块内存,程序的可用内存也就少一块; 当程序…

java设计模式(十二)享元模式(Flyweight Pattern)

1、模式介绍&#xff1a; 享元模式是一种结构型设计模式&#xff0c;旨在通过共享对象来有效支持大量细粒度的对象。它通过将对象的状态分为内部状态&#xff08;可共享&#xff09;和外部状态&#xff08;不可共享&#xff09;来减少内存消耗和提高性能。内部状态存储在享元对…

SpringMVC基础详解

文章目录 一、SpringMVC简介1、什么是MVC2、MVC架构模式与三层模型的区别3、什么是SpringMVC 二、HelloWorld程序1、pom文件2、springmvc.xml3、配置web.xml文件4、html文件5、执行Controller 三、RequestMapping注解1、value属性1.1、基础使用1.2、Ant风格&#xff08;模糊匹配…

详细分析Java中@RequiredArgsConstructor注解的基本知识(附Demo)

目录 前言1. 基本知识2. 源码解读3. Demo3.1 简易Demo3.2 staticName属性3.3 onConstructor属性3.4 access属性 4. AllArgsConstructor比较 前言 从源码中学习&#xff0c;事因是看到项目代码中有所引用 RequiredArgsConstructor 是 Lombok 提供的一个注解&#xff0c;用于自…

容器:deque

以下是对于deque容器知识的整理 1、构造 2、赋值 3、大小操作 4、插入 5、删除 6、数据存取 7、排序 #include <iostream> #include <deque> #include <algorithm> using namespace std; /* deque容器&#xff1a;双端数组&#xff0c;可以对头端进行插入删…

2024年06月CCF-GESP编程能力等级认证Scratch图形化编程四级真题解析

本文收录于《Scratch等级认证CCF-GESP图形化真题解析》专栏,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 一、单选题(共 10 题,每题 2 分,共 30 分) 第1题 小杨父母带他到某培训机构给他报名参加 CCF 组织的 GESP 认证考试的第 1 级,那他可以选择的认证语言有几…

树状数组——点修区查与区修点查

树状数组是一种代码量小&#xff0c;维护区间的数据结构 他可以实现&#xff1a; 1.区间修改&#xff0c;单点查询 2.单点修改&#xff0c;区间查询 当然&#xff0c;二者不可兼得&#xff0c;大人全都要的话&#xff0c;请选择线段树 前置知识&#xff1a; lowbit(x)操作…

zerotier-one自建根服务器方法四

一、简介 前面几篇文章已经写完了安装配置服务器&#xff0c;今天写一下客户端如何连接自建的服务器。 二、准备工作 准备一个有公网IP的云主机。 要稳定性、安全性、不差钱的可以使用阿里、腾讯等大厂的云服务器。 本人穷屌丝一枚&#xff0c;所以我用的是免费的“三丰云…

常见sql语句练习

Tips&#xff1a;之前查看网上的文章感觉太乱了&#xff0c;所以自己整理了一套sql语句来练习&#xff0c;主要也可以拿来应对面试&#xff0c;需要的可以自行下载练习 包含基本语句、聚合函数、模糊查询、范围查询、排序、聚合、分组、分页、子查询、索引和视图、左右连接、双…

商城积分系统的代码实现(下)-- 积分订单的退款与结算

一、接着上文 用户在消耗积分的时候&#xff0c;需要根据一定的逻辑&#xff0c;除了扣减账户的当前余额&#xff0c;还需要依次消费积分订单的余额。 private void updatePointsOrderByUse(Integer schoolId, Long userId, String pointsType, int usingPoints) {List<Po…

数字证书与PKI解析

目录 1. 什么是数字证书 2. 为什么需要数字证书 3. 数字证书的格式 4. 什么是PKI 5. PKI的组成要素组件 5.1 用户 5.2 认证机构&#xff08;CA&#xff09; 5.3 仓库 5.4 PKI的体系结构 5.4.1 层次结构模型 5.4.2 交叉证明模型 5.4.3 混合模型 1. 什么是数字证书 要…

Django任意URL跳转漏洞(CVE-2018-14574)

目录 Django介绍 URL跳转漏洞介绍 Django任意URL跳转漏洞介绍 环境搭建 防御方法 前段时间在面试时&#xff0c;问到了URL跳转漏洞&#xff0c;我没有回答好&#xff0c;下午把URL跳转漏洞学习了&#xff0c;发现也不难&#xff0c;看来还需要学习的东西很多呀&#xff0c…

burp靶场xss漏洞(中级篇)下

靶场地址 All labs | Web Security Academy 第九关&#xff1a;反射型&#xff08; 转义&#xff09; 1.在搜索框随机输入字符并用Burp抓包 2.测试不同字符在JavaScript字符串中的反映&#xff0c;发现查询结果被包裹在script标签中 而单引号会被转义为 \ 3.构造payload跳出j…

Qt开发报错:Q_INTERFACES Error: Undefined interface

1、背景 VS2019qt5.12.10 从svn拉下来的项目&#xff0c;结果报错&#xff1a; Q_INTERFACES Error: Undefined interface 之前在VS的扩展中在线安装了qt插件&#xff0c; 安装了一半&#xff0c;比较慢&#xff0c;直接强行退出了。。 后来安装了qt官网的插件。。。。 2、报…

OpenCV 调用自定义训练的 YOLO-V8 Onnx 模型

一、YOLO-V8 转 Onnx 在本专栏的前面几篇文章中&#xff0c;我们使用 ultralytics 公司开源发布的 YOLO-V8 模型&#xff0c;分别 Fine-Tuning 实验了 目标检测、关键点检测、分类 任务&#xff0c;实验后发现效果都非常的不错&#xff0c;但是前面的演示都是基于 ultralytics…