前端实现简易版线上文件编辑器

前言

今天发现两个有意思的API, showDirectoryPickershowSaveFilePicker, 他们有什么用呢, 一言蔽之, 可以使用户在浏览器环境打开文件夹以及保存文件, 这么有意思的API既然被我看到了,那就好好开发下,简单做了个线上文件编辑器, 这是源码地址, 我们可以简单从源码去了解一下这两个API

简单介绍

showDirectoryPicker

showDirectoryPicker() 方法方法用于显示一个目录选择器,以允许用户选择一个目录,它的返回值为一个 Promise 对象,会兑现一个 FileSystemDirectoryHandle 对象。

showSaveFilePicker

showSaveFilePicker() 方法用于显示一个文件选择器,以允许用户保存一个文件。可以选择一个已有文件覆盖保存,也可以输入名字新建一个文件. 它的返回值为 一个 Promise 对象,会兑现一个 FileSystemFileHandle 对象。

FileSystemDirectoryHandle

FileSystemDirectoryHandle 接口提供了一个文件系统目录的句柄。它的实例属性和方法都是从FileSystemHandle, 同时它提供了一些异步迭代器的方法:

  • entries(): 返回给定对象自己的可枚举属性[key, value]对的新的异步迭代器。
  • keys(): 返回一个新的异步迭代器,其中包含FileSystemDirectoryHandle中每个项的键
  • values(): 返回一个新的异步迭代器,其中包含FileSystemDirectoryHandle对象中每个索引的值
  • @@asyncIterator\](): 默认情况下返回entries函数

FileSystemFileHandle对象是一个代表文件的对象,它提供了一些方法来获取和操作文件,例如:

  • getFile:返回一个Promise对象,用于获取文件;
  • createSyncAccessHandle:返回一个FileSystemSyncAccessHandle对象,用于同步访问文件;
  • createWritable:返回一个Promise对象,用于创建一个可写流,用于写入文件;

使用API实现线上文件编辑器

首先脑子里明确一下效果,参考一下VSCODE, 左边是选择区,右边是编辑区,大概长这样(简陋了点)

然后我们选择了文件之后的效果大概是这样

好的,需求确定,那么开始写代码,逐步实现思路如下:

  1. 首先搭建一个 vue3 项目(直接原生一把梭也行),删掉一些自带的无用代码,给css做点简单的初始化

  2. APP.vue 引入两个组件, 左边的就叫Menu.vue,右边的就叫Editor.vue,写个左右布局

  3. 首先是实现左边的选择文件(Menu.vue),分析一下需求,左边菜单大概分两个状态:

    1. 一个是未选择,显示一个选择按钮,
    2. 一个是已选择,显示一个树状的文件夹结构
  4. 我们先从未选择开始实现,写一个按钮, 给按钮绑定一个事件 onOpenDirectory, 我们需要这个事件让用户选择一个文件夹,同时把用户选择的文件夹结构全部递归成一个树,显示出来,代码实现如下:

javascript 复制代码
const onOpenDirectory = async () => {
  fileContent.value = ''
  try {
    // 获取文件夹
    const handle = await showDirectoryPicker()
    fileTree.value = [await processFileTree(handle)]
  } catch {
    // 拒绝了授权
    ElMessage.info('您取消了操作')
  }
}

// 所有的 IO 操作都是异步的, 因为用 async await 递归出所有的文件
const processFileTree = async (handle) => {
  // handle.kind -> directory: 文件夹; file: 文件
  if (handle.kind == 'file') {
    return handle
  }
  handle.children = []
  const iterator = handle.entries()
  for await (const item of iterator) {
    const subItem = await processFileTree(item[1])
    handle.children.push(subItem)
  }

  return handle
}

showDirectoryPicker 这个API会给我们返回一个FileSystemDirectoryHandle对象,我们前面简单介绍了FileSystemDirectoryHandle这个对象,他有一个entries()方法会返回一个迭代器,我们可以通过递归这个迭代器去获取到一个树状的数据结构(这里注意,所有的IO操作都是异步的,请牢记这点),我们通过一个递归函数 processFileTree去拿到这个树的数据并定义一个响应式数据 fileTree 去接收它,同时显示出这个树状菜单(菜单我使用了 element-plus 的el-tree,懒得写UI了,按需引入 element-plus),到此,我们的文件夹菜单已经生成完毕

  1. 我们在点击左侧菜单时,如果是文件类型,那么我们需要给它显示在右边提供给用户去编辑,那我们来实现这个功能,给el-tree添加一个 @node-click="handleNodeClick" 实现,接着去实现 handleNodeClick 这个函数: 代码如下:
ini 复制代码
const handleNodeClick = async (node) => {
  // 渲染 file 的内容
  if (node.kind === 'file') {
    fileName.value = node.name
    const file = await node.getFile()
    const render = new FileReader()
    render.onload = (e) => {
      fileContent.value = e.target.result
    }
    render.readAsText(file, 'utf-8')
  }
}

这个就比较简单了,判断点击的node类型,如果是文件,那么就 new FileReader 对象去读取, 在onload中拿到整个文件的内容,这里由于我们要把数据给到兄弟组件 Editor.vue 中使用,所以我使用了 provide + inject, 这里使用 props + emit 也可以, 看个人选择,在app.vue中定义一个变量 fileContent 同时通过provide('fileContent', fileContent)给到后代元素去使用,在Menu.vueconst fileContent = inject('fileContent') 获取这个值,同时在render.onload回调里,将fileContent的值修改以便于Editor.vue使用,至此,我们左边菜单的功能已经全部实现了

  1. 接着我们去实现编辑区的功能,先定义定义一个 props 去接受 fileContent(也可以通过 inject),这里代码高亮的效果,我们可以使用 highlightjs 实现,首先 npm i highlight.js,再装一个vue 的highlight组件npm i @highlightjs/vue-plugin, 接着即可直接使用了。

  2. 虽然此时已经显示好了,高亮也有了,但是此时文件还不能编辑, 我们通过 contenteditable 属性来让该区域可编辑, 全局属性 contenteditable 是一个枚举属性,表示元素是否可被用户编辑。如果可以,浏览器会修改元素的组件以允许编辑,具体使用可以看 MDN, 这里不多赘述,此时编辑也实现了,我们需要实现保存功能,简单定义一个按钮,用于文件的保存,此时Editor.vue代码如下:

xml 复制代码
<template>
  <div class="container">
    <div v-show="props.content">
      <div class="title-box">
        <el-button size="small" type="primary" @click="handleSaveFile">保存文件</el-button>
      </div>
      <highlightjs ref="highlightRef" autodetect contenteditable="true" :code="props.content" />
    </div>
  </div>
</template>
  1. 接着我们就可以实现保存文件的功能了, handleSaveFile 函数用于保存,具体代码如下:
javascript 复制代码
const handleSaveFile = async () => {
  const content = highlightRef.value.$el.textContent
  const fileHandle = await window.showSaveFilePicker()
  const writableStream = await fileHandle.createWritable()
  await writableStream.write(content)
  await writableStream.close()
}

先获取到编辑区域的内容 content, 然后使用showSaveFilePicker() 前面我们说过showSaveFilePicker() 的返回值 FileSystemFileHandle 有个 createWritable 创建可读写的流,我们需要的就是这个,我们使用 fileHandle.createWritable()write(content) 把内容写进文件中, 这里记住,完成之后一定要调用close() 方法去关闭流,至此我们整个功能已经实现好了,大家可以自行美化一下这个简易的线上文件编辑

总结

通过 showDirectoryPickershowSaveFilePicker 我们可以实现文件的读取和写入保存,挺有意思的,不过 mdn 提示我们这是个实验性技术,在各位投入生产前一定要留意浏览器的支持性。

相关推荐
进击的尘埃17 分钟前
策略模式和状态模式到底啥区别?拿审批流表单说个明白
javascript
vim怎么退出21 分钟前
谷歌性能优化知识点总结
前端
专业抄代码选手23 分钟前
在react中,TSX是如何转变成JS的
前端·javascript
葡萄城技术团队23 分钟前
【实践篇】从零到一:手把手教你搭建一套企业级 SpreadJS 协同设计器
前端
进击的尘埃1 小时前
SOLID 原则在 React 组件库里怎么落地:五个重构案例
javascript
忆江南1 小时前
# iOS Block 深度解析
前端
米丘1 小时前
vue-router v5.x 路由模式关于 createWebHistory、 createWebHashHistory的实现
前端
本末倒置1831 小时前
Bun 内置模块全解析:告别第三方依赖,提升开发效率
前端·javascript·node.js
踩着两条虫1 小时前
AI 驱动的 Vue3 应用开发平台 深入探究(二):核心概念之DSL模式与数据模型
前端·vue.js·ai编程
进击的尘埃1 小时前
中介者模式:把面板之间的蜘蛛网拆干净
javascript