前言

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

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

顶部栏

咱们会实现一个顶部栏去治理以后关上的文件,这里次要用到的是antd的Tabs组件。整体的交互如下:

  • 点击左侧树的文件时:

    • 以后文件不在顶部栏内,关上比聚焦该文件
    • 如果曾经存在,聚焦该文件
  • 点击顶部栏的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)的插件。GFM 是 GitHub 对规范 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' //国际化jsonimport '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)  }}/>

在内容更新的时候会触发一个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仓库来搭建了一个图床。

首先关上你的GitHub点击新建仓库(这里我因为曾经创立过了所以显示仓库已存在。),而后关上 GitHub token治理页面,新建一个token。

新建的时候把这里钩上

而后点击生成,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 API 的 contents 端点,以上传文件。

上传胜利之后,能够通过jsdelivr的CDN服务包裹一下咱们的图片链接,让咱们的图片资源拜访的更快: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编辑软件,这篇文章就是在这个软件下写进去的。大体性能没有什么问题,就是还有一些小的交互细节能够继续去优化一下。如果你感觉有意思的话,点点关注点点赞吧~