文档中的恐龙

Dinos in the document

假设你需要在文档中包含一些奇特的、站点特有的元素。它们可能是对系统内其他对象(文档、用户等)的句柄,也可能是领域相关的组件,或者,就像这个演示一样,是恐龙。

ProseMirror 允许你定义自己的 schema,其中包括定义自定义的文档元素。你在 schema 中放入的任何东西,都可以作为文档中合适的语义元素来使用。

在本示例中,我们用一个新的节点来扩展基础 schema。首先,我们定义一个 node spec,它描述节点的行为及其 DOM 表示。

// The supported types of dinosaurs.
const dinos = ["brontosaurus", "stegosaurus", "triceratops",
               "tyrannosaurus", "pterodactyl"]

const dinoNodeSpec = {
  // Dinosaurs have one attribute, their type, which must be one of
  // the types defined above.
  // Brontosaurs are still the default dino.
  attrs: {type: {default: "brontosaurus"}},
  inline: true,
  group: "inline",
  draggable: true,

  // These nodes are rendered as images with a `dino-type` attribute.
  // There are pictures for all dino types under /img/dino/.
  toDOM: node => ["img", {"dino-type": node.attrs.type,
                          src: "/img/dino/" + node.attrs.type + ".png",
                          title: node.attrs.type,
                          class: "dinosaur"}],
  // When parsing, such an image, if its type matches one of the known
  // types, is converted to a dino node.
  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 模块,来提供编辑器的基础设施。但我们还需要在插入菜单中添加新的菜单项。首先,定义一个处理恐龙插入的命令

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"

// Ask example-setup to build its basic menu
let menu = buildMenuItems(dinoSchema)
// Add a dino-inserting item for each type of dino
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 和菜单来创建编辑器状态和视图。

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,
    // Pass exampleSetup our schema and the menu we created
    plugins: exampleSetup({schema: dinoSchema, menuContent: menu.fullMenu})
  })
})