文档中的恐龙

产品给了你一个需求:你需要在你的文档中包含一些独家的文档元素。 用户可以操作这些元素或者开发者自己可以生成这些元素。下面的例子就展示给你这样一个「独家」的恐龙元素。

ProseMirror 允许你定义你自己的 schemas,它包含了自定义的文档元素。你可以在文档中使用任何你放到 schema 中的元素,当然如果 schema 中没有的元素你是不能够用的。

Remix on Glitch

在这个示例中,我们扩展了 basic schema,将一个单独的新的节点加入其中。 首先,我们定义一个 node spec,它描述了节点的行为和它的 DOM 表现形式。

// 支持的恐龙类型
const dinos = ["brontosaurus", "stegosaurus", "triceratops",
               "tyrannosaurus", "pterodactyl"]

const dinoNodeSpec = {
  // 恐龙只有一个属性,那就是它的类型,而且必须是上面的类型之一
  // Brontosaurs 是默认的类型
  attrs: {type: {default: "brontosaurus"}},
  inline: true,
  group: "inline",
  draggable: true,

  // 这些节点以一个带有 `dino-type` 属性的 images 节点进行渲染
  // 在 /img/dino/ 目录下有所有的恐龙图片
  toDOM: node => ["img", {"dino-type": node.attrs.type,
                          src: "/img/dino/" + node.attrs.type + ".png",
                          title: node.attrs.type,
                          class: "dinosaur"}],
  // 当格式化一个 image DOM 的时候,如果它的 type 属性是上面所述的恐龙类型之一,那么它就会被转换成一个 dino 节点
  parseDOM: [{
    tag: "img[dino-type]",
    getAttrs: dom => {
      let type = dom.getAttribute("dino-type")
      return dinos.indexOf(type) > -1 ? {type} : false
    }
  }]
}

之后,我们创建了一个真实的 schema 来包含这个节点,然后使用它去格式化 HTML 到 ProseMirror 文档:

import {Schema, DOMParser} from "prosemirror-model"
import {schema} from "prosemirror-schema-basic"

const dinoSchema = new Schema({
  nodes: schema.spec.nodes.addBefore("image", "dino", dinoNodeSpec),
  marks: schema.spec.marks
})

let content = document.querySelector("#content")
let startDoc = DOMParser.fromSchema(dinoSchema).parse(content)

这个示例再次使用了 example setup 模块,以提供一些基本的编辑行为。 不过我们在菜单栏需要一个新的菜单项,来插入该节点。所以首先,定义一个 command 来处理恐龙的插入:

let dinoType = dinoSchema.nodes.dino

function insertDino(type) {
  return function(state, dispatch) {
    let {$from} = state.selection, index = $from.index()
    if (!$from.parent.canReplaceWith(index, index, dinoType))
      return false
    if (dispatch)
      dispatch(state.tr.replaceSelectionWith(dinoType.create({type})))
    return true
  }
}

然后,新建一个菜单项来调用我们的命令:

import {MenuItem} from "prosemirror-menu"
import {buildMenuItems} from "prosemirror-example-setup"

// 让 example-setup 去 build 它的基本菜单
let menu = buildMenuItems(dinoSchema)
// 增加一个插入恐龙节点的按钮
dinos.forEach(name => menu.insertMenu.content.push(new MenuItem({
  title: "Insert " + name,
  label: name.charAt(0).toUpperCase() + name.slice(1),
  enable(state) { return insertDino(name)(state) },
  run: insertDino(name)
})))

现在,就只剩下用我们自定义的 schema 和 menu 来创建一个编辑器 state 和 view:

import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {exampleSetup} from "prosemirror-example-setup"

window.view = new EditorView(document.querySelector("#editor"), {
  state: EditorState.create({
    doc: startDoc,
    // 传给 exampleSetup 和 我们创建的 menu
    plugins: exampleSetup({schema: dinoSchema, menuContent: menu.fullMenu})
  })
})