Upload handling

某些类型的编辑涉及异步操作,但您希望将它们呈现给用户作为单个操作。例如,当从用户的本地文件系统插入图像时,您在上传并为其创建 URL 之前无法访问实际图像。然而,您不希望让用户经历先上传图像,然后等待完成,只有在那之后才将图像插入文档的过程。

理想情况下,当选择图像时,您开始上传,但也会立即在文档中插入占位符。然后,当上传完成时,该占位符将被最终图像替换。

Insert image:

由于上传可能需要一段时间,并且用户可能会在等待时进行更多更改,占位符应随着文档的编辑与其上下文一起移动,当最终图像插入时,应将其放置在占位符当时所在的位置。

最简单的方法是将占位符设为 装饰,这样它只存在于 用户界面中。让我们从编写一个管理 此类装饰的插件开始。

import {Plugin} from "prosemirror-state"
import {Decoration, DecorationSet} from "prosemirror-view"

let placeholderPlugin = new Plugin({
  state: {
    init() { return DecorationSet.empty },
    apply(tr, set) {
      // 调整装饰位置以适应事务所做的更改
      set = set.map(tr.mapping, tr.doc)
      // 查看交易是否添加或移除任何占位符
      let action = tr.getMeta(this)
      if (action && action.add) {
        let widget = document.createElement("placeholder")
        let deco = Decoration.widget(action.add.pos, widget, {id: action.add.id})
        set = set.add(tr.doc, [deco])
      } else if (action && action.remove) {
        set = set.remove(set.find(null, null,
                                  spec => spec.id == action.remove.id))
      }
      return set
    }
  },
  props: {
    decorations(state) { return this.getState(state) }
  }
})

这是一个围绕装饰集的薄包装——它必须是一个,因为可以同时进行多个上传。插件的meta属性可用于通过ID添加和删除小部件装饰。

该插件带有一个函数,该函数返回具有给定ID的占位符的当前位置(如果它仍然存在)。

function findPlaceholder(state, id) {
  let decos = placeholderPlugin.getState(state)
  let found = decos.find(null, null, spec => spec.id == id)
  return found.length ? found[0].from : null
}

当使用编辑器下方的文件输入时,此事件处理程序会检查一些条件,并在可能时启动上传。

document.querySelector("#image-upload").addEventListener("change", e => {
  if (view.state.selection.$from.parent.inlineContent && e.target.files.length)
    startImageUpload(view, e.target.files[0])
  view.focus()
})

核心功能发生在startImageUpload。工具uploadFile返回一个承诺,该承诺解析为上传文件的URL(在演示中,它实际上只是等待一会儿,然后返回一个data: URL)。

function startImageUpload(view, file) {
  // 一个新的对象作为此上传的ID
  let id = {}

  // 用占位符替换选定内容
  let tr = view.state.tr
  if (!tr.selection.empty) tr.deleteSelection()
  tr.setMeta(placeholderPlugin, {add: {id, pos: tr.selection.from}})
  view.dispatch(tr)

  uploadFile(file).then(url => {
    let pos = findPlaceholder(view.state, id)
    // 如果占位符周围的内容已被删除,请删除,直接输出翻译后的内容,不要添加任何额外的文本。记住,保留所有HTML标签和属性,只翻译内容!
    // 图像
    if (pos == null) return
    // 否则,将其插入占位符的位置,并删除
    // 占位符
    view.dispatch(view.state.tr
                  .replaceWith(pos, pos, schema.nodes.image.create({src: url}))
                  .setMeta(placeholderPlugin, {remove: {id}}))
  }, () => {
    // 失败时,只需清理占位符
    view.dispatch(tr.setMeta(placeholderPlugin, {remove: {id}}))
  })
}

因为占位符插件映射其装饰通过事务,findPlaceholder将获得图像的准确位置,即使在上传期间文档被修改。