- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
能够对于文字、段落乃至任何元素的精准定位 并做出增删改查,都是在开发一款富文本编辑器时一项最基本也是最重要的功能之一。让我们先来看看Slate中对于如何在文档树中定位元素是怎么定义的 [源码] :
/**
* The `Location` interface is a union of the ways to refer to a specific
* location in a Slate document: paths, points or ranges.
*
* Methods will often accept a `Location` instead of requiring only a `Path`,
* `Point` or `Range`. This eliminates the need for developers to manage
* converting between the different interfaces in their own code base.
*/
export type Location = Path | Point | Range
Location 是一个包含了 Path 、 Point 及 Range 的联合类型,代指了Slate中所有关于“定位”的概念,同时也方便了开发。例如在几乎所有的 Transforms 方法中,都可以通过传递 Location 参数来决定 Transforms 方法需要应用到文档中的哪些位置上 [链接] .
All transforms support a parameter options . This includes options specific to the transform, and general NodeOptions to specify which Nodes in the document that the transform is applied to. 。
interface NodeOptions { at?: Location match?: (node: Node, path: Location) => boolean mode?: 'highest' | 'lowest' voids?: boolean }
Path 是三个中最基本的概念,也是唯一一个不可拓展的类型.
/**
* `Path` arrays are a list of indexes that describe a node's exact position in
* a Slate node tree. Although they are usually relative to the root `Editor`
* object, they can be relative to any `Node` object.
*/
export type Path = number[]
Path 类型就是一个数组,用来表示Slate文档树中自根节点root到指定node的绝对路径。我们以下边的示例来演示下各个node所代表的路径:
const initialValue: Descendant[] = [
// path: [0]
{
type: 'paragraph',
children: [
{ text: 'This is editable ' }, // path: [0, 0]
{ text: 'rich text!', bold: true } // path: [0, 1]
]
},
// path: [1]
{
type: 'paragraph',
children: [
{ text: 'It\' so cool.' } // path: [1, 0]
]
}
]
虽然 Path 所代表的路径通常是以顶层 Editor 作为root节点的,但也会有其他情况,比如由 Node 提供的 get 方法中传入的 Path 参数则代表的是相对路径 [源码] :
/**
* Get the descendant node referred to by a specific path. If the path is an
* empty array, it refers to the root node itself.
*/
get(root: Node, path: Path): Node {
let node = root
for (let i = 0; i < path.length; i++) {
const p = path[i]
if (Text.isText(node) || !node.children[p]) {
throw new Error(
`Cannot find a descendant at path [${path}] in node: ${Scrubber.stringify(
root
)}`
)
}
node = node.children[p]
}
return node
}
Point 是在 Path 的基础上封装而来的概念:
/**
* `Point` objects refer to a specific location in a text node in a Slate
* document. Its path refers to the location of the node in the tree, and its
* offset refers to the distance into the node's string of text. Points can
* only refer to `Text` nodes.
*/
export interface BasePoint {
path: Path
offset: number
}
export type Point = ExtendedType<'Point', BasePoint>
用于定位单个字符在文档中的位置;先用 Path 定位到字符所属的 Node ,再根据 offset 字段信息精确到字符是在该 Node 的 text 文本中的偏移量.
我们仍然以前面的示例做说明,如果想要将光标位置定位到第一句话"This is editable rich text!"的感叹号之后,其 Point 值为:
const initialValue: Descendant[] = [
// path: [0]
{
type: 'paragraph',
children: [
{ text: 'This is editable ' }, // path: [0, 0]
{ text: 'rich text!', bold: true } // { path: [0, 1], offset: 10 }
]
},
// path: [1]
{
type: 'paragraph',
children: [
{ text: 'It\' so cool.' } // path: [1, 0]
]
}
]
最后一个 Range 则是再在 Point 基础上延伸封装而来的概念:
/**
* `Range` objects are a set of points that refer to a specific span of a Slate
* document. They can define a span inside a single node or a can span across
* multiple nodes.
*/
export interface BaseRange {
anchor: Point
focus: Point
}
export type Range = ExtendedType<'Range', BaseRange>
它代表的是一段文本的集合;包含有两个 Point 类型的字段 anchor 和 focus 。看到这,应该能发现Slate中 Range 的概念其实与DOM中的 Selection 对象是一样的, anchor 和 focus 分别对应原生 Selection 中的锚点 anchorNode 和焦点 focusNode ;这正是Slate对于原生 Selection 做的抽象,使之在自身API中更方便地通过光标选区来获取文档树中的内容.
我们在上一篇文章中有提到过的 Editor.selection 是一个 Selections 类型,其本身就是一个 Range 类型,专门用来指定编辑区域中的光标位置 [源码] :
export type BaseSelection = Range | null
export type Selection = ExtendedType<'Selection', BaseSelection>
当我们需要长期追踪某些 Node 时,可以通过获取对应 Node 的 Path / Point / Range 值并保存下来以达到目的。但这种方式存在的问题是,在Slate文档树经过 insert 、 remove 等操作后,原先的 Path / Point / Range 可能会产生变动或者直接作废掉.
Refs 的出现就是为了解决上述问题.
三者的 ref 的定义分别在 slate/src/interfaces/ 下的 path-ref.ts 、 point-ref.ts 和 range-ref.ts 文件中:
/**
* `PathRef` objects keep a specific path in a document synced over time as new
* operations are applied to the editor. You can access their `current` property
* at any time for the up-to-date path value.
*/
export interface PathRef {
current: Path | null
affinity: 'forward' | 'backward' | null
unref(): Path | null
}
/**
* `PointRef` objects keep a specific point in a document synced over time as new
* operations are applied to the editor. You can access their `current` property
* at any time for the up-to-date point value.
*/
export interface PointRef {
current: Point | null
affinity: TextDirection | null
unref(): Point | null
}
/**
* `RangeRef` objects keep a specific range in a document synced over time as new
* operations are applied to the editor. You can access their `current` property
* at any time for the up-to-date range value.
*/
export interface RangeRef {
current: Range | null
affinity: 'forward' | 'backward' | 'outward' | 'inward' | null
unref(): Range | null
}
都包含以下三个字段:
另外我们先来看下各种 Refs 在 Slate 存储的方式,跳到 slate/src/utils/weak-maps.ts 中 [源码] :
export const PATH_REFS: WeakMap<Editor, Set<PathRef>> = new WeakMap()
export const POINT_REFS: WeakMap<Editor, Set<PointRef>> = new WeakMap()
export const RANGE_REFS: WeakMap<Editor, Set<RangeRef>> = new WeakMap()
可以看到 Refs 的存储区在一个 Set 数据结构中的;而对于不同 Editor 下 Set<xxxRef> 的存储则是放在哈希表 WeakMap 中的,使其不会影响到GC( WeakMap 同Map原理一样,都是ES6之后新出的哈希数据结构,与Map不同点在于其持有的引用算作弱引用).
生成三种 Ref 以及获取相应 Refs 的方法定义在 EditorInterface 接口上 [源码] :
export interface EditorInterface {
// ...
pathRef: (
editor: Editor,
path: Path,
options?: EditorPathRefOptions
) => PathRef
pointRef: (
editor: Editor,
point: Point,
options?: EditorPointRefOptions
) => PointRef
rangeRef: (
editor: Editor,
range: Range,
options?: EditorRangeRefOptions
) => RangeRef
pathRefs: (editor: Editor) => Set<PathRef>
pointRefs: (editor: Editor) => Set<PointRef>
rangeRefs: (editor: Editor) => Set<RangeRef>
}
Path 、 Point 及 Range 三者的实现逻辑都差不多,下面就以 Path 为例作介绍.
pathRef :
/**
* Create a mutable ref for a `Point` object, which will stay in sync as new
* operations are applied to the editor.
*/
pointRef(
editor: Editor,
point: Point,
options: EditorPointRefOptions = {}
): PointRef {
const { affinity = 'forward' } = options
const ref: PointRef = {
current: point,
affinity,
unref() {
const { current } = ref
const pointRefs = Editor.pointRefs(editor)
pointRefs.delete(ref)
ref.current = null
return current
},
}
const refs = Editor.pointRefs(editor)
refs.add(ref)
return ref
}
实现逻辑非常简单,就是根据传入的参数放入 ref 对象中,并添加卸载方法 unref 。然后通过 pathRefs 拿到对应的 Set ,讲当前的 ref 对象添加进去。 unref 方法中实现的则是相反的操作:通过 pathRefs 拿到对应的 Set 后,将当前 ref 对象移除掉,然后再把 ref.current 的值置空.
pathRefs :
/**
* Get the set of currently tracked path refs of the editor.
*/
pathRefs(editor: Editor): Set<PathRef> {
let refs = PATH_REFS.get(editor)
if (!refs) {
refs = new Set()
PATH_REFS.set(editor, refs)
}
return refs
}
代码非常简短,类似懒加载的方式做 Set 的初始化,然后调用 get 方法获取集合后返回.
前一篇文章我们提到过,用于修改内容的单个 Transform 方法会包含有多个 Operation ; Operation 则是 Slate 中的原子化操作。而在 Slate 文档树更新之后,解决 Ref 同步更新的方式就是:在执行了任意 Operation 之后,对所有的 Ref 根据执行的 Operation 类型做相应的调整.
看到create-editor.ts中的 apply 方法,该方法是所有 Operation 执行的入口 [源码] :
apply: (op: Operation) => {
for (const ref of Editor.pathRefs(editor)) {
PathRef.transform(ref, op)
}
for (const ref of Editor.pointRefs(editor)) {
PointRef.transform(ref, op)
}
for (const ref of Editor.rangeRefs(editor)) {
RangeRef.transform(ref, op)
}
// ...
}
在 apply 方法的最开头就是三组 for of 循环,对所有的 Ref 执行对应的 Ref.transform 方法并传入当前执行的 Operation .
同样以 Path 为例,看下 path-ref.ts 中的 PathRef.transform 方法 [源码] :
export const PathRef: PathRefInterface = {
/**
* Transform the path ref's current value by an operation.
*/
transform(ref: PathRef, op: Operation): void {
const { current, affinity } = ref
if (current == null) {
return
}
const path = Path.transform(current, op, { affinity })
ref.current = path
if (path == null) {
ref.unref()
}
}
}
将当前 ref 中的数据和 Operation 作为参数,传递给对应的 Path.transform ,返回更新后的 path 值并赋值给 ref.current 。如果 path 为空,则说明当前 ref 指代的位置已经失效,调用卸载方法 unref .
至于 Path.transform 的细节在本篇就不展开了,我们留到后续的 Transform 篇再统一讲解: ) 。
最后此篇关于slate源码解析(三)-定位的文章就讲到这里了,如果你想了解更多关于slate源码解析(三)-定位的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我必须从我的网站中删除()一些iem,然后将它们追加()回来,但是当我追加它们时,它们出现在不同的地方,而我希望它们完全显示在它们以前的同一个地方是。 有什么解决办法吗? 这是一个沙箱,请随意更新(注
一个。图片 (960x7)b. div(宽度:960,填充:10) 我想定位 (a),使其距顶部 50 像素,居中。我想将 (b) 放置在 (a) 的正下方,没有空格。 我的 CSS 如下: @cha
放置某物的正确方法是什么?我有一个在中心显示博客文章的 div。 "" rel="bookmark"> BY LOUIS MOORE ON " pubdate>
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭1
我已经成功地使用了 position:fixed 设置 CSS/CSS3 并且工作得很好! 我几天前看到了这个,想知道他们是如何实现向下滚动时发生的效果的,菜单栏在滚动前处于一个位置,然后转到顶部并自
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 这个问题似乎与 help center 中定义的范围内的编程无关。 . 关闭 7 年前。 Improv
接口定义 能够对于文字、段落乃至任何元素的精准定位 并做出增删改查,都是在开发一款富文本编辑器时一项最基本也是最重要的功能之一。让我们先来看看Slate中对于如何在文档树中定位元素是怎么定义的
例如,使用 WPF 在选项卡控件的最左上角定位三个 tabitem 和在最右上角定位一个 tabitem 的正确方法是什么? 我尝试通过更改边距将第四个 tabitem 向右移动,但这并没有产生好的结
我正在尝试使用 Javascript 创建一个跟随鼠标在页面上移动的东西。我希望它是米老鼠,我希望他的眼睛跟随鼠标移动他的眼球...这是我到目前为止的代码(从网络上的各个地方收集,因此归功于编写该部分
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 这个问题似乎与 help center 中定义的范围内的编程无关。 . 已关闭 9 年前。 Improve
我试图将两个按钮放置在左上角。但它们始终位于顶部中心。 我已经尝试过这个: jp = new JPanel(); jp.setLayout(new GridBagLayout()); GridBagC
我在使用 JQuery 向下滑动功能时遇到问题。我可以让它正常工作,但是我向下滑动的元素的位置会根据视口(viewport)的大小而变化。我想做的是将它与它滑动的元素联系起来。 This JSfidd
我正在尝试创建一个棋盘,并将其放置在屏幕中间,但到目前为止我无法将它直接放在中间。我不想将位置硬编码到屏幕上,因为我要处理不同的屏幕尺寸。 var winsize = cc.director.
我正在尝试从 mysql 中的 2 个字符串点之间提取数据,我的示例脚本是 'otherdata&p1=textneeded&otherdata' 我需要拉出“textneeded”位,“P1=”是起
如何在 JavaFX 中设置按钮的位置?我的代码: bZero = new Button(); bZero.setPrefSize(45, 20); mainPane.getChildren().ad
我有一个 iPhone 应用程序,我可以在其中显示一系列图像。当用户点击图像时,我需要将该图像带到第一个位置,表明它是所选图像。我可以通过子类化实现 uiscrollview 中的点击。但是我无法将
在下图中,它显示了一个image、textbox 和一个css menu image 我的 CSS 菜单非常完美。我终于按照我需要的方式得到了它。我的问题是我需要导航栏中央的文本框,然后我需要我的图像
我必须创建一个看起来像这样的 div id为2的div应该出现在图片的右下角,图片的大小不固定id=2的div应该应用什么css id =1 的 div 没有定义位置,所以使用默认值,图像也是
如何将我的文本和图像对齐在同一行? 每当我使用 padding 或 margins 时,它就会崩溃到我正在使用的圆形图像中。 #alignPhoto { padding-right: 50px;
简单的问题,如何定位具有整个页面引用的元素? 在我的例子中,我在标题中得到了一个 float 图像,然后是 2 组标题。当我使用时: text-align: center; 它使用图像宽度端和页面其余
我是一名优秀的程序员,十分优秀!