Tooltips
我使用「Tooltips」意指一个小的 UI 元素显示于文档之上。这对于编辑器来说将其用来显示控制菜单或者额外的信息很有用,比如
显示一个类似于「Medium」风格的编辑 UI 组件(Medium 是一个知名的博客平台),它的控制按钮一般是隐藏的,除非你选中了一些文本。
此时它会在选区上方弹出来一个小的悬浮菜单。
在 ProseMirror 中通常有两种途径来实现这个 tooltips 效果。最简单的方式是插入一个 widget decorations(装饰器)
然后绝对定位之。如果你没有指定一个位置信息(即含有 left
和 bottom
的对象),该 widget 就会出现在你默认放置它的地方(新建一个 widget 需要一个 ProseMirror 中的位置 pos 坐标)。
如果该 tooltips 依赖一个你指定的位置的话,这挺好用的。
如果你想将一些东西置于选区之上,或者你想对这些元素使用动画,亦或者你需要能够允许当编辑器的 overflow
属性不是 visible
的时候,这个 tooltips 能够固定在编辑器之外(例如让
编辑器能滚动),那么上面的 decoration(装饰器)可能不太可行。在这种场景下,你将需要「手动」的来定位你的 tooltips。
选择一些文本来让 tooltips 显示出来,它带有选区的字符数信息。
(这可能不是一个有用的 tooltips,不过确实一个不错的基本示例)
不过你仍然可以充分利用 ProseMirror 的更新逻辑,以让 tooltips 和编辑器的 state 保持同步。我们可以使用 plugin view(插件视图) 来创建一个 view component(视图组件)
来将其加到编辑器的更新周期中。
import {Plugin} from "prosemirror-state"
let selectionSizePlugin = new Plugin({
view(editorView) { return new SelectionSizeTooltip(editorView) }
})
实际的 view 会创建一个 DOM 节点来表示该 tooltips,然后将其插入到编辑器文档中。
class SelectionSizeTooltip {
constructor(view) {
this.tooltip = document.createElement("div")
this.tooltip.className = "tooltip"
view.dom.parentNode.appendChild(this.tooltip)
this.update(view, null)
}
update(view, lastState) {
let state = view.state
if (lastState && lastState.doc.eq(state.doc) &&
lastState.selection.eq(state.selection)) return
if (state.selection.empty) {
this.tooltip.style.display = "none"
return
}
this.tooltip.style.display = ""
let {from, to} = state.selection
let start = view.coordsAtPos(from), end = view.coordsAtPos(to)
let box = this.tooltip.offsetParent.getBoundingClientRect()
let left = Math.max((start.left + end.left) / 2, start.left + 3)
this.tooltip.style.left = (left - box.left) + "px"
this.tooltip.style.bottom = (box.bottom - start.top) + "px"
this.tooltip.textContent = to - from
}
destroy() { this.tooltip.remove() }
}
无论何时编辑器的 state 更新了,它都会检查是否需要去更新 tooltips。它的位置计算可能有点麻烦,但是 CSS 就是这样。
基本上讲,它使用了 ProseMirror 的 coordsAtPos
方法 来找到选区的位置坐标,
然后使用该左边设置了一个 left
和 bottom
属性以让该 tooltips 相对于父级节点进行偏移,该父级节点指的是理它最近的 position 属性是 relative 或者 absolute 的元素。