Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement findPath without relying on DOM #5800

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/plenty-glasses-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'slate-dom': minor
'slate': minor
---

Move findPath api from DOMEditor to Editor
8 changes: 8 additions & 0 deletions packages/slate-dom/src/plugin/dom-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ export const DOMEditor: DOMEditorInterface = {
},

findPath: (editor, node) => {
const newVersionPath = editor.findPath(node)
const path: Path = []
let child = node

Expand All @@ -395,6 +396,13 @@ export const DOMEditor: DOMEditorInterface = {

if (parent == null) {
if (Editor.isEditor(child)) {
if (!Path.equals(newVersionPath, path)) {
throw new Error(
`path mismatch, expected ${JSON.stringify(
path
)}, got ${JSON.stringify(newVersionPath)}`
)
}
return path
} else {
break
Expand Down
9 changes: 9 additions & 0 deletions packages/slate/src/core/normalize-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Element } from '../interfaces/element'
import { Transforms } from '../interfaces/transforms'
import { Descendant, Node } from '../interfaces/node'
import { Editor } from '../interfaces/editor'
import { getNodeToParent } from '../editor/find-path'

export const normalizeNode: WithEditorFirstArg<Editor['normalizeNode']> = (
editor,
Expand All @@ -26,6 +27,14 @@ export const normalizeNode: WithEditorFirstArg<Editor['normalizeNode']> = (
return
}

// Cache dirty nodes child-parent relationships for editor.findPath()
if (!Text.isText(node)) {
const nodeToParent = getNodeToParent(editor)
node.children.forEach(child => {
nodeToParent.set(child, node)
})
}

// Determine whether the node should have block or inline children.
const shouldHaveInlines = Editor.isEditor(node)
? false
Expand Down
5 changes: 5 additions & 0 deletions packages/slate/src/create-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
edges,
elementReadOnly,
end,
findPath,
first,
fragment,
getVoid,
Expand Down Expand Up @@ -94,6 +95,9 @@ export const createEditor = (): Editor => {
operations: [],
selection: null,
marks: null,
_caches: {
nodeToParent: undefined,
},
isElementReadOnly: () => false,
isInline: () => false,
isSelectable: () => true,
Expand Down Expand Up @@ -130,6 +134,7 @@ export const createEditor = (): Editor => {
edges: (...args) => edges(editor, ...args),
elementReadOnly: (...args) => elementReadOnly(editor, ...args),
end: (...args) => end(editor, ...args),
findPath: (...args) => findPath(editor, ...args),
first: (...args) => first(editor, ...args),
fragment: (...args) => fragment(editor, ...args),
getMarks: (...args) => marks(editor, ...args),
Expand Down
58 changes: 58 additions & 0 deletions packages/slate/src/editor/find-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Text } from '../interfaces/text'
import { BaseEditor, EditorInterface } from '../interfaces/editor'
import { Ancestor } from '../interfaces/node'
import { Scrubber } from '../interfaces/scrubber'

export const findPath: EditorInterface['findPath'] = (editor, node) => {
if (node === editor) {
return []
}

const nodeToParent = getNodeToParent(editor)

const parent = nodeToParent.get(node)
if (!parent) {
throw new Error(
`Unable to find the path for Slate node (parent not found): ${Scrubber.stringify(
node
)}`
)
}
Comment on lines +14 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could occur during normal use if editor.children is modified in a way that bypasses normaliseNode, such as when setting editor.children directly or when finding the path of a newly added node inside withoutNormalizing. I'd suggest traversing the full tree to repair the cache in this case, and only throw an error if the parent still cannot be found. In addition, the withoutNormalizing case can be mitigated by updating the cache in editor.apply instead of normalizeNode.


const parentPath = findPath(editor, parent)

const index = parent.children.indexOf(node)
if (index < 0) {
throw new Error(
`Unable to find the path for Slate node (node is not child of its parent): ${Scrubber.stringify(
node
)}`
)
Comment on lines +26 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar situation to above

}

return [...parentPath, index]
}

export function getNodeToParent(editor: BaseEditor) {
let nodeToParent = editor._caches.nodeToParent
if (nodeToParent) {
return nodeToParent
}

nodeToParent = new WeakMap()
editor._caches.nodeToParent = nodeToParent
const parents: Ancestor[] = [editor]

let parent = parents.pop()
while (parent) {
for (const child of parent.children) {
nodeToParent.set(child, parent)
if (!Text.isText(child)) {
parents.push(child)
}
}
parent = parents.pop()
}

return nodeToParent
}
1 change: 1 addition & 0 deletions packages/slate/src/editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './delete-fragment'
export * from './edges'
export * from './element-read-only'
export * from './end'
export * from './find-path'
export * from './first'
export * from './fragment'
export * from './get-void'
Expand Down
22 changes: 22 additions & 0 deletions packages/slate/src/interfaces/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export interface BaseEditor {
operations: Operation[]
marks: EditorMarks | null

/**
* @internal Caches for internal use.
*/
_caches: EditorCaches

// Overrideable core methods.

apply: (operation: Operation) => void
Expand Down Expand Up @@ -122,6 +127,7 @@ export interface BaseEditor {
edges: OmitFirstArg<typeof Editor.edges>
elementReadOnly: OmitFirstArg<typeof Editor.elementReadOnly>
end: OmitFirstArg<typeof Editor.end>
findPath: OmitFirstArg<typeof Editor.findPath>
first: OmitFirstArg<typeof Editor.first>
fragment: OmitFirstArg<typeof Editor.fragment>
getMarks: OmitFirstArg<typeof Editor.marks>
Expand Down Expand Up @@ -180,6 +186,13 @@ export type Selection = ExtendedType<'Selection', BaseSelection>

export type EditorMarks = Omit<Text, 'text'>

export interface EditorCaches {
/**
* Editor children snapshot after the last flush.
*/
nodeToParent: WeakMap<Descendant, Ancestor> | undefined
}

export interface EditorAboveOptions<T extends Ancestor> {
at?: Location
match?: NodeMatch<T>
Expand Down Expand Up @@ -389,6 +402,11 @@ export interface EditorInterface {
*/
first: (editor: Editor, at: Location) => NodeEntry

/**
* Find the path of Slate node.
*/
findPath: (editor: Editor, node: Node) => Path

/**
* Get the fragment at a location.
*/
Expand Down Expand Up @@ -767,6 +785,10 @@ export const Editor: EditorInterface = {
return editor.end(at)
},

findPath(editor, node) {
return editor.findPath(node)
},

first(editor, at) {
return editor.first(at)
},
Expand Down
Loading