Electron实现你自己的Markdown编辑软件

简介: Electron实现你自己的Markdown编辑软件

Electron实现你自己的Markdown编辑软件

前言

公众号:【可乐前端】,期待关注交流,分享一些有意思的前端知识

在上一期我们已经实现了文件管理的功能,这篇文章主要会介绍文件顶部栏实现、Markdown编辑器的主体接入以及图床实现。

顶部栏

image.png

我们会实现一个顶部栏去管理当前打开的文件,这里主要用到的是antdTabs组件。整体的交互如下:

  • 点击左侧树的文件时:
  • 当前文件不在顶部栏内,打开比聚焦该文件
  • 如果已经存在,聚焦该文件
  • 点击顶部栏的tab,切换到改文件进行编辑
  • 点击关闭按钮,关闭对应的tab

点击左侧树的时候,可以如下实现:

  const [tabs, setTabs] = useState([])
  const [activeKey, setActiveKey] = useState('')
  const [value, setValue] = useState('')
  const ipcRenderer = window.electron.ipcRenderer
  const handleSelect = (keys, { node }) => {
    const key = keys[0]
    if (!node.isLeaf) {
      return
    }
    const newTabs = [...tabs]
    const exist = newTabs.find((tab) => tab.key === key)
    if (!exist) {
      newTabs.push({ key, label: node.title })
      setTabs(newTabs)
    }
    setActiveKey(key)
  }
  // ...
   <Tabs
      hideAdd
      type="editable-card"
      activeKey={activeKey}
      onChange={(key) => setActiveKey(key)}
      onEdit={onEdit}
      items={tabs}
   />

这样点击的时候,对应的文件就会出现在顶部tab中,然后我们需要根据tab对应的文件路径去读取相应的文件内容。此时渲染进程可以向主进程发送一个事件,主进程读取到文件内容之后返回给渲染进程,渲染进程再交给编辑器处理。

  useEffect(() => {
    if (!activeKey) {
      return
    }
    ipcRenderer.send(GET_FILE, activeKey)
  }, [activeKey])

监听到当前活跃的tab变更之后,向主进程发送事件,主进程接收到事件之后,就可以进行如下的读取文件操作:

  ipcMain.on(GET_FILE, (event, key) => {
    try {
      const res = fs.readFileSync(key, { encoding: 'utf8' })
      event.sender.send(RECEIVE_FILE, res)
    } catch (error) {
      event.sender.send(COMMON_ERROR, '读取文件失败')
      event.sender.send(COMMON_ERROR_LOG, error)
    }
  })

这样渲染进程就可以获取到打开的文件内容。

然后来看一下移除标签页的逻辑,根据标签对应的key,从标签页数组中找到对应的项,检查是否要移除的标签页是当前活动标签页(activeKey),如果是的话,需要更新当前活跃的标签页。

如果移除的标签页不是最后一个标签页,就将当前活跃标签页设置为上一个标签页的key,否则设置为第一个标签页的key。最后,如果标签页数组为空,则把activeKey置空。

  const onEdit = (targetKey) => {
    const remove = (targetKey) => {
      let newActiveKey = activeKey
      let lastIndex = -1
      tabs.forEach((item, i) => {
        if (item.key === targetKey) {
          lastIndex = i - 1
        }
      })
      const newPanes = tabs.filter((item) => item.key !== targetKey)
      if (newPanes.length && newActiveKey === targetKey) {
        if (lastIndex >= 0) {
          newActiveKey = newPanes[lastIndex].key
        } else {
          newActiveKey = newPanes[0].key
        }
      }
      if (newPanes.length === 0) {
        newActiveKey = ''
      }
      setTabs(newPanes)
      setActiveKey(newActiveKey)
    }
    remove(targetKey)
  }

顶部栏标签管理就实现到这里,下面我们来接入Markdown编辑器的主体。

编辑器主体

因为之前一直写文章用的都是掘金的Markdown编辑器,所以这里我也是直接接入了它的开源版本,由于我使用的是React技术栈,所以需要用到的包是@bytemd/react

然后还可以安装一些常用的插件,比如@bytemd/plugin-gfm,它是专门处理 GitHub 风格的 Markdown 扩展语法(GitHub Flavored Markdown,简称 GFM)的插件。GFMGitHub 对准 Markdown 扩展的一种,引入了一些额外的功能,比如说任务列表、删除线、表格等;还有@bytemd/plugin-highlight,这是一个代码高亮的插件。

安装完对应的依赖之后,就可以通过十分简单的代码来使用这个组件了。

import gfm from '@bytemd/plugin-gfm'
import highlight from '@bytemd/plugin-highlight'
import { Editor } from '@bytemd/react'
import 'bytemd/dist/index.css'
import zh from 'bytemd/locales/zh_Hans.json' //国际化json
import 'highlight.js/styles/default.css'
import './editor.css' // 额外的markdown主题样式
const plugins = [gfm(), highlight()]

const [value, setValue] = useState('')
<Editor
  uploadImages={handleUpload}
  mode="split"
  locale={zh}
  value={value}
  plugins={plugins}
  onChange={(v) => {
    setValue(v)
    updateFile(v)
  }}
/>

image.png

在内容更新的时候会触发一个onChange事件,这跟平时一般的input组件表现一致,这个时候我们需要更新组件的value以及更新对应文件的内容。

  const updateFile = useCallback(
    debounce((value) => {
      ipcRenderer.send(UPDATE_FILE, activeKey, value)
    }, 300),
    [activeKey]
  )

实现一个updateFile来更新文件的内容,这里我加了一个防抖函数,让IO不要太过频繁。同样也是发送一个事件给主进程,让主进程去写文件。

  ipcMain.on(UPDATE_FILE, (event, key, value) => {
    try {
      console.log('更新文件内容')
      fs.writeFileSync(key, value, { encoding: 'utf8' })
    } catch (error) {
      event.sender.send(COMMON_ERROR, '更新文件失败')
      event.sender.send(COMMON_ERROR_LOG, error)
    }
  })

这样我们的编辑器主体接入就完成了,可以开始快乐的写文章啦。

图床

写文章的时候怎么能不配图呢?配图怎么能少的了图床呢?所以这一小节是基于GitHub仓库来搭建了一个图床。

image.png

首先打开你的GitHub点击新建仓库(这里我由于已经创建过了所以显示仓库已存在。),然后打开 GitHub token管理页面,新建一个token

image.png

新建的时候把这里钩上

image.png

然后点击生成,token就新建好了,请注意保管好。

然后我们就可以开始尝试把文件上传到GitHub了:


import axios from 'axios'
export const generateRandomFileName = (file) => {
  return `${window.crypto.randomUUID()}.${file.type.split('/')[1]}`
}

function fileToBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()

    reader.onload = () => {
      resolve(reader.result.split(',')[1])
    }

    reader.onerror = (error) => {
      reject(error)
    }

    reader.readAsDataURL(file)
  })
}

const token = '你的token'
const owner = '你的用户名'
const repo = '仓库名'
const commitMessage = 'Upload image to GitHub'
export const uploadImageToGitHub = async (file) => {
  try {
    const path = generateRandomFileName(file)
    // 构造请求头
    const headers = {
      Authorization: `token ${token}`,
      'Content-Type': 'application/json'
    }
    const imageContent = await fileToBase64(file)
    // 构造请求体
    const requestData = {
      message: commitMessage,
      content: imageContent,
      path: path
    }

    // 发送 HTTP 请求
    const response = await axios.put(
      `https://api.github.com/repos/${owner}/${repo}/contents/${path}`,
      requestData,
      { headers }
    )

    // 输出上传结果
    console.log('Image uploaded successfully:', response.data)
    return `https://cdn.jsdelivr.net/gh/${owner}/${repo}@main/${response.data.content.path}`
  } catch (error) {
    console.error('Failed to upload image to GitHub:', error.response.data)
    return ''
  }
}

让我们一起看看上面的代码做了什么:

  • generateRandomFileName 函数:接收一个文件对象 file,通过使用 window\.crypto.randomUUID() 生成一个随机的文件名,保证文件名的唯一性,使用文件的类型(file.type)作为文件扩展名。
  • fileToBase64 函数:将文件对象转换为 base64 编码的字符串。
  • uploadImageToGitHub 函数:接收一个文件对象 file,通过调用前面两个函数,生成随机的文件名和将文件转换为 base64 编码的字符串。然后,使用 Axios 库发送一个 PUT 请求到 GitHub APIcontents 端点,以上传文件。

上传成功之后,可以通过jsdelivrCDN服务包裹一下我们的图片链接,让我们的图片资源访问的更快:https://cdn.jsdelivr.net/gh/${owner}/${repo}@main/${response.data.content.path}

到这里我们就已经实现了将File对象上传到GitHub的功能,剩下需要做的就是在编辑器组件中接入这个上传功能。

bytemd暴露了uploadImages这个属性,当我们在编辑器中上传、粘贴图片时会触发这个方法,我们可以在这里拿到我们在本地上传的图片,然后调用上传到GitHub的接口,这样就实现了在编辑器中上传图片。

  const handleUpload = async (files) => {
    const urls = await Promise.all(
      files.map(async (file) => {
        const url = await uploadImageToGitHub(file)
        return {
          url
        }
      })
    )
    return urls
  }

最后

到这里我们就已经实现了自己的Markdown编辑软件,这篇文章就是在这个软件下写出来的。大体功能没有什么问题,就是还有一些小的交互细节可以持续去优化一下。如果你觉得有意思的话,点点关注点点赞吧~

相关文章
|
1月前
|
存储 安全 关系型数据库
Blossom:开源私有部署的markdown笔记软件
Blossom 是一款功能强大的开源笔记软件,支持私有部署,可将笔记、图片、个人计划等数据保存在自己的服务器中,并实现实时同步。它还具备动态博客功能,方便记录和分享内容。Blossom 支持多种设备,提供完善的文件管理、快速迁移和丰富的附加功能,是个人知识管理和博客展示的理想选择。
69 7
Blossom:开源私有部署的markdown笔记软件
|
17天前
|
机器学习/深度学习 人工智能 自然语言处理
模型训练数据-MinerU一款Pdf转Markdown软件
MinerU是由上海人工智能实验室OpenDataLab团队开发的开源智能数据提取工具,专长于复杂PDF文档的高效解析与提取。它能够将含有图片、公式、表格等多模态内容的PDF文档转化为Markdown格式,同时支持从网页和电子书中提取内容,显著提升了AI语料准备的效率。MinerU具备高精度的PDF模型解析工具链,能自动识别乱码,保留文档结构,并将公式转换为LaTeX格式,广泛适用于学术、财务、法律等领域。
102 4
|
2月前
|
iOS开发 MacOS Python
【10月更文挑战第1天】「Mac上学Python 1」入门篇1 - 安装Typora与Markdown编辑技巧
本篇将详细介绍如何在Mac系统上安装Typora这款简洁高效的Markdown编辑器,并学习Markdown常用语法。通过本篇,用户能够准备好记录学习笔记的工具,并掌握基本的文档编辑与排版技巧,为后续学习提供便利。
163 1
【10月更文挑战第1天】「Mac上学Python 1」入门篇1 - 安装Typora与Markdown编辑技巧
|
2月前
|
开发工具
用 Vim 编辑 Markdown 时直接粘贴图片
介绍一款 Vim 插件,可以帮助提升编辑 Markdown 时的从剪贴板粘贴图片的效率和体验。
33 2
SublimeText配置Markdown编辑及预览
本文详细介绍了如何配置Sublime Text及相关插件,使之成为Markdown编辑器并且能够在浏览器中实现预览功能。
|
5月前
|
JSON 监控 数据挖掘
上网行为管理软件中的Markdown文档编写与格式化
Markdown是轻量级标记语言,用于快速排版和格式化文本,常见于技术文档和博客。基本语法包括:#(1-6个)创建标题,空行分隔段落,*或-创建列表,[文本](链接)插入链接,![文本](图片URL)插入图片。此外,还有代码块(```包围)、引用(&gt;)等高级格式。Markdown支持HTML嵌入以自定义样式,并广泛应用于上网行为管理等领域的文档编写。通过自动化脚本,能将监控数据提交至网站进行分析。
54 6
|
5月前
|
前端开发
PC端01,桌面端,electron的开发,electron的开发的系列课程,软件开发必备流程,electron的讲解,electron的开发,vitepress博主的gitee链接,PC端效率软件
PC端01,桌面端,electron的开发,electron的开发的系列课程,软件开发必备流程,electron的讲解,electron的开发,vitepress博主的gitee链接,PC端效率软件
PC端01,桌面端,electron的开发,electron的开发的系列课程,软件开发必备流程,electron的讲解,electron的开发,vitepress博主的gitee链接,PC端效率软件
|
7月前
|
人工智能 前端开发 开发者
编辑技能:使用 Markdown 标记语言仅仅靠键盘也能完成排版,鼠标兄弟可以一边歇着了
Markdown 是一种标记语言,对于 SaaS 服务商和 AI 提示词编写者来说是必备技能。它使用简单的符号如 `#`, `-`, `&gt;` 进行文本排版,无需复杂富文本编辑器。通过键盘输入即可实现标题、列表等样式,比 WPS 或 Word 更便捷。Markdown 广泛应用于云平台社区、问答网站、GitHub 项目文档及开源程序。了解基础后,适应不同平台的自定义语法相对容易,且有多种 Markdown 客户端工具可供选择,如 Typora、MarkText 和 Obsidian。Visual Studio Code 配合插件也是强大工具,适合撰写公众号推文等。
62 4
|
7月前
|
API CDN
Electron Markdown编辑器实战:资源管理器实现
Electron Markdown编辑器实战:资源管理器实现
|
7月前
|
编解码
🖥️Electron实现录屏软件(二)——指定区域录制
🖥️Electron实现录屏软件(二)——指定区域录制