友好的 Markdown

Friendly Markdown

想象一下,你有一个允许用户输入评论的网站,并且你决定用 Markdown 作为评论的输入方式。你的目标用户大多熟悉 Markdown,并且觉得它很方便。但你可能也有一些非技术用户,对他们来说,学习晦涩的语法规则并不是一件自然而然的事。

无需修改任何后端代码,你就可以把 ProseMirror 作为一个替代的输入编辑器接入进去。用户甚至可以在编辑过程中随时在这两种视图之间切换!

prosemirror-markdown 包定义了一个 ProseMirror schema,它能够精确表达 Markdown 所能表达的内容。它还附带了一个解析器和序列化器,用于在该 schema 的文档与 Markdown 文本之间进行相互转换。

为了把真正的编辑器抽象出来,我们首先围绕一个 textarea 创建一个简单的接口:

class MarkdownView {
  constructor(target, content) {
    this.textarea = target.appendChild(document.createElement("textarea"))
    this.textarea.value = content
  }

  get content() { return this.textarea.value }
  focus() { this.textarea.focus() }
  destroy() { this.textarea.remove() }
}

然后为支持 Markdown 的 ProseMirror 实例实现同样的接口。这个接口的输入和输出仍然是 Markdown 文本,它在内部将其转换为 ProseMirror 文档。

import {EditorView} from "prosemirror-view"
import {EditorState} from "prosemirror-state"
import {schema, defaultMarkdownParser,
        defaultMarkdownSerializer} from "prosemirror-markdown"
import {exampleSetup} from "prosemirror-example-setup"

class ProseMirrorView {
  constructor(target, content) {
    this.view = new EditorView(target, {
      state: EditorState.create({
        doc: defaultMarkdownParser.parse(content),
        plugins: exampleSetup({schema})
      })
    })
  }

  get content() {
    return defaultMarkdownSerializer.serialize(this.view.state.doc)
  }
  focus() { this.view.focus() }
  destroy() { this.view.destroy() }
}

最后,我们可以接上一些单选按钮,让用户在这两种表示之间切换。

let place = document.querySelector("#editor")
let view = new MarkdownView(place, document.querySelector("#content").value)

document.querySelectorAll("input[type=radio]").forEach(button => {
  button.addEventListener("change", () => {
    if (!button.checked) return
    let View = button.value == "markdown" ? MarkdownView : ProseMirrorView
    if (view instanceof View) return
    let content = view.content
    view.destroy()
    view = new View(place, content)
    view.focus()
  })
})