mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-11-24 03:39:01 +08:00
feature: nodepos (#4701)
* start writing docs for nodepos * added nodepos docs * added to links --------- Co-authored-by: bdbch <dominik@bdbch.com>
This commit is contained in:
parent
fcd5cc89fb
commit
57eba08f46
@ -32,6 +32,7 @@
|
||||
"iframe-resizer": "^4.3.2",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-import": "^15.1.0",
|
||||
"prosemirror-dev-tools": "^4.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"sass": "^1.49.7",
|
||||
|
339
docs/api/nodepos.md
Normal file
339
docs/api/nodepos.md
Normal file
@ -0,0 +1,339 @@
|
||||
# Node Positions
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Usage](#usage)
|
||||
- [API](#api)
|
||||
- [NodePos](#nodepos)
|
||||
- [Methods](#methods)
|
||||
- [constructor](#constructor)
|
||||
- [closest](#closest)
|
||||
- [querySelector](#queryselector)
|
||||
- [querySelectorAll](#queryselectorall)
|
||||
- [setAttributes](#setattributes)
|
||||
- [Properties](#properties)
|
||||
- [node](#node)
|
||||
- [element](#element)
|
||||
- [content](#content)
|
||||
- [attributes](#attributes)
|
||||
- [textContent](#textcontent)
|
||||
- [depth](#depth)
|
||||
- [pos](#pos)
|
||||
- [size](#size)
|
||||
- [from](#from)
|
||||
- [to](#to)
|
||||
- [range](#range)
|
||||
- [parent](#parent)
|
||||
- [before](#before)
|
||||
- [after](#after)
|
||||
- [children](#children)
|
||||
- [firstChild](#firstchild)
|
||||
- [lastChild](#lastchild)
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
Node Positions (short `NodePos`) are a new concept introduced with Tiptap 3.0.0. They are used to describe a specific position of a node, it's children, it's parent and easy ways to navigate between them. They are heavily inspired by the DOM and are based on Prosemirror's [ResolvedPos](https://prosemirror.net/docs/ref/#model.ResolvedPos) implementation.
|
||||
|
||||
## Usage
|
||||
|
||||
The easiest way to create a new **Node Position** is to use the helper functions added to the Editor instance. This way you always use the correct editor instance and have direct access to the API.
|
||||
|
||||
```js
|
||||
// set up your editor somewhere up here
|
||||
|
||||
// The NodePosition for the outermost document node
|
||||
const $doc = editor.$doc
|
||||
|
||||
// This will get all nodes with the type 'heading' currently found in the document
|
||||
const $headings = editor.$nodes('heading')
|
||||
|
||||
// You can also combine this to filter by attributes
|
||||
const $h1 = editor.$nodes('heading', { level: 1 })
|
||||
|
||||
// You can also pick nodes directly:
|
||||
const $firstHeading = editor.$node('heading', { level: 1 })
|
||||
|
||||
// If you don't know the type but the position you want to work with, you can create a new NodePos via the $pos method
|
||||
const $myCustomPos = editor.$pos(30)
|
||||
```
|
||||
|
||||
You can also create your own NodePos instances:
|
||||
|
||||
```js
|
||||
// You need to have an editor instance
|
||||
// and a position you want to map to
|
||||
const myNodePos = new NodePos(100, editor)
|
||||
```
|
||||
|
||||
## What can I do with a NodePos?
|
||||
|
||||
NodePos can be used to traverse the document similar to the document DOM of your browser. You can access the parent node, child nodes and the sibling nodes. Here are an example of what you can do with a `codeBlock` node:
|
||||
|
||||
```js
|
||||
// get the first codeBlock from your document
|
||||
const $codeBlock = editor.$node('codeBlock')
|
||||
|
||||
// get the previous NodePos of your codeBlock node
|
||||
const $previousItem = $codeBlock.before
|
||||
|
||||
// easily update the content
|
||||
$previousItem.content = '<p>Updated content</p>'
|
||||
```
|
||||
|
||||
If you are familiar with the DOM the following example should look familiar to you:
|
||||
|
||||
```js
|
||||
// get a bullet list from your doc
|
||||
const $bulletList = editor.$node('bulletList')
|
||||
|
||||
// get all listItems from your bulletList
|
||||
const $listItems = $bulletList.querySelectorAll('listItem')
|
||||
|
||||
// get the last listItem
|
||||
const $lastListItem = $listItems[0]
|
||||
|
||||
// insert a new listItem after the last one
|
||||
editor.commands.insertContentAt($lastListItem.after, '<li>New item</li>')
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### NodePos
|
||||
|
||||
The NodePos class is the main class you will work with. It is used to describe a specific position of a node, it's children, it's parent and easy ways to navigate between them. They are heavily inspired by the DOM and are based on Prosemirror's [ResolvedPos](https://prosemirror.net/docs/ref/#model.ResolvedPos) implementation.
|
||||
|
||||
#### Methods
|
||||
|
||||
##### constructor
|
||||
|
||||
**Arguments**
|
||||
- `pos` – The position you want to map to
|
||||
- `editor` – The editor instance you want to use
|
||||
|
||||
**Returns** `NodePos`
|
||||
|
||||
```js
|
||||
const myNodePos = new NodePos(100, editor)
|
||||
```
|
||||
|
||||
##### closest
|
||||
|
||||
The closest NodePos instance of your NodePosition going up the depth. If there is no matching NodePos, it will return `null`.
|
||||
|
||||
**Returns** `NodePos | null`
|
||||
|
||||
```js
|
||||
const closest = myNodePos.closest('bulletList')
|
||||
```
|
||||
|
||||
##### querySelector
|
||||
|
||||
The first matching NodePos instance of your NodePosition going down the depth. If there is no matching NodePos, it will return `null`.
|
||||
|
||||
You can also filter by attributes via the second attribute.
|
||||
|
||||
**Returns** `NodePos | null`
|
||||
|
||||
```js
|
||||
const firstHeading = myNodePos.querySelector('heading')
|
||||
const firstH1 = myNodePos.querySelector('heading', { level: 1 })
|
||||
```
|
||||
|
||||
##### querySelectorAll
|
||||
|
||||
All matching NodePos instances of your NodePosition going down the depth. If there is no matching NodePos, it will return an empty array.
|
||||
|
||||
You can also filter by attributes via the second attribute.
|
||||
|
||||
**Returns** `Array<NodePos>`
|
||||
|
||||
```js
|
||||
const headings = myNodePos.querySelectorAll('heading')
|
||||
const h1s = myNodePos.querySelectorAll('heading', { level: 1 })
|
||||
```
|
||||
|
||||
##### setAttributes
|
||||
|
||||
Set attributes on the current NodePos.
|
||||
|
||||
**Returns** `NodePos`
|
||||
|
||||
```js
|
||||
myNodePos.setAttributes({ level: 1 })
|
||||
```
|
||||
|
||||
#### Properties
|
||||
|
||||
##### node
|
||||
|
||||
The Prosemirror Node at the current Node Position.
|
||||
|
||||
**Returns** `Node`
|
||||
|
||||
```js
|
||||
const node = myNodePos.node
|
||||
node.type.name // 'paragraph'
|
||||
```
|
||||
|
||||
##### element
|
||||
|
||||
The DOM element at the current Node Position.
|
||||
|
||||
**Returns** `Element`
|
||||
|
||||
```js
|
||||
const element = myNodePos.element
|
||||
element.tagName // 'P'
|
||||
```
|
||||
|
||||
##### content
|
||||
|
||||
The content of your NodePosition. This can be set to a new value to update the content of the node.
|
||||
|
||||
**Returns** `string`
|
||||
|
||||
```js
|
||||
const content = myNodePos.content
|
||||
myNodePos.content = '<p>Updated content</p>'
|
||||
```
|
||||
|
||||
##### attributes
|
||||
|
||||
The attributes of your NodePosition.
|
||||
|
||||
**Returns** `Object`
|
||||
|
||||
```js
|
||||
const attributes = myNodePos.attributes
|
||||
attributes.level // 1
|
||||
```
|
||||
|
||||
##### textContent
|
||||
|
||||
The text content of your NodePosition.
|
||||
|
||||
**Returns** `string`
|
||||
|
||||
```js
|
||||
const textContent = myNodePos.textContent
|
||||
```
|
||||
|
||||
##### depth
|
||||
|
||||
The depth of your NodePosition.
|
||||
|
||||
**Returns** `number`
|
||||
|
||||
```js
|
||||
const depth = myNodePos.depth
|
||||
```
|
||||
|
||||
##### pos
|
||||
|
||||
The position of your NodePosition.
|
||||
|
||||
**Returns** `number`
|
||||
|
||||
```js
|
||||
const pos = myNodePos.pos
|
||||
```
|
||||
|
||||
##### size
|
||||
|
||||
The size of your NodePosition.
|
||||
|
||||
**Returns** `number`
|
||||
|
||||
```js
|
||||
const size = myNodePos.size
|
||||
```
|
||||
|
||||
##### from
|
||||
|
||||
The from position of your NodePosition.
|
||||
|
||||
**Returns** `number`
|
||||
|
||||
```js
|
||||
const from = myNodePos.from
|
||||
```
|
||||
|
||||
##### to
|
||||
|
||||
The to position of your NodePosition.
|
||||
|
||||
**Returns** `number`
|
||||
|
||||
```js
|
||||
const to = myNodePos.to
|
||||
```
|
||||
|
||||
##### range
|
||||
|
||||
The range of your NodePosition.
|
||||
|
||||
**Returns** `number`
|
||||
|
||||
```js
|
||||
const range = myNodePos.range
|
||||
```
|
||||
|
||||
##### parent
|
||||
|
||||
The parent NodePos of your NodePosition.
|
||||
|
||||
**Returns** `NodePos`
|
||||
|
||||
```js
|
||||
const parent = myNodePos.parent
|
||||
```
|
||||
|
||||
##### before
|
||||
|
||||
The NodePos before your NodePosition. If there is no NodePos before, it will return `null`.
|
||||
|
||||
**Returns** `NodePos | null`
|
||||
|
||||
```js
|
||||
const before = myNodePos.before
|
||||
```
|
||||
|
||||
##### after
|
||||
|
||||
The NodePos after your NodePosition. If there is no NodePos after, it will return `null`.
|
||||
|
||||
**Returns** `NodePos | null`
|
||||
|
||||
```js
|
||||
const after = myNodePos.after
|
||||
```
|
||||
|
||||
##### children
|
||||
|
||||
The child NodePos instances of your NodePosition.
|
||||
|
||||
**Returns** `Array<NodePos>`
|
||||
|
||||
```js
|
||||
const children = myNodePos.children
|
||||
```
|
||||
|
||||
##### firstChild
|
||||
|
||||
The first child NodePos instance of your NodePosition. If there is no child, it will return `null`.
|
||||
|
||||
**Returns** `NodePos | null`
|
||||
|
||||
```js
|
||||
const firstChild = myNodePos.firstChild
|
||||
```
|
||||
|
||||
##### lastChild
|
||||
|
||||
The last child NodePos instance of your NodePosition. If there is no child, it will return `null`.
|
||||
|
||||
**Returns** `NodePos | null`
|
||||
|
||||
```js
|
||||
const lastChild = myNodePos.lastChild
|
||||
```
|
@ -167,6 +167,8 @@
|
||||
link: /api/introduction
|
||||
- title: Editor
|
||||
link: /api/editor
|
||||
- title: Node Positions
|
||||
link: /api/nodepos
|
||||
- title: Commands
|
||||
link: /api/commands
|
||||
items:
|
||||
|
300
package-lock.json
generated
300
package-lock.json
generated
@ -77,6 +77,7 @@
|
||||
"iframe-resizer": "^4.3.2",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-import": "^15.1.0",
|
||||
"prosemirror-dev-tools": "^4.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"sass": "^1.49.7",
|
||||
@ -1743,6 +1744,18 @@
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/@compiled/react": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@compiled/react/-/react-0.11.4.tgz",
|
||||
"integrity": "sha512-mtnEUFM7w/5xABWWWj3wW0vjS/cHSg0PAttJC+hOpQ5z5qGZCwk43Gy8Hfjruxvll73igJ5DSMzcAyek6DMKjw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cypress/request": {
|
||||
"version": "2.88.10",
|
||||
"dev": true,
|
||||
@ -5167,6 +5180,12 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/base16": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz",
|
||||
"integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.4.3",
|
||||
"dev": true,
|
||||
@ -5208,6 +5227,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.202",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
|
||||
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
"version": "3.0.5",
|
||||
"dev": true,
|
||||
@ -6428,6 +6453,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base16": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
|
||||
"integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"funding": [
|
||||
@ -7070,6 +7101,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
|
||||
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.3",
|
||||
"color-string": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"dev": true,
|
||||
@ -7083,6 +7124,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/color-support": {
|
||||
"version": "1.1.3",
|
||||
"dev": true,
|
||||
@ -8148,6 +8199,12 @@
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/diff-match-patch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
||||
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"dev": true,
|
||||
@ -9944,6 +10001,57 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/html": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html/-/html-1.0.0.tgz",
|
||||
"integrity": "sha512-lw/7YsdKiP3kk5PnR1INY17iJuzdAtJewxr14ozKJWbbR97znovZ0mh+WEMZ8rjc3lgTK+ID/htTjuyGKB52Kw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"concat-stream": "^1.4.7"
|
||||
},
|
||||
"bin": {
|
||||
"html": "bin/html.js"
|
||||
}
|
||||
},
|
||||
"node_modules/html/node_modules/concat-stream": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
|
||||
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
|
||||
"dev": true,
|
||||
"engines": [
|
||||
"node >= 0.8"
|
||||
],
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^2.2.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/html/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/html/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "7.2.0",
|
||||
"dev": true,
|
||||
@ -10971,6 +11079,64 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jotai": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-1.13.1.tgz",
|
||||
"integrity": "sha512-RUmH1S4vLsG3V6fbGlKzGJnLrDcC/HNb5gH2AeA9DzuJknoVxSGvvg8OBB7lke+gDc4oXmdVsaKn/xDUhWZ0vw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "*",
|
||||
"@babel/template": "*",
|
||||
"jotai-devtools": "*",
|
||||
"jotai-immer": "*",
|
||||
"jotai-optics": "*",
|
||||
"jotai-redux": "*",
|
||||
"jotai-tanstack-query": "*",
|
||||
"jotai-urql": "*",
|
||||
"jotai-valtio": "*",
|
||||
"jotai-xstate": "*",
|
||||
"jotai-zustand": "*",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@babel/template": {
|
||||
"optional": true
|
||||
},
|
||||
"jotai-devtools": {
|
||||
"optional": true
|
||||
},
|
||||
"jotai-immer": {
|
||||
"optional": true
|
||||
},
|
||||
"jotai-optics": {
|
||||
"optional": true
|
||||
},
|
||||
"jotai-redux": {
|
||||
"optional": true
|
||||
},
|
||||
"jotai-tanstack-query": {
|
||||
"optional": true
|
||||
},
|
||||
"jotai-urql": {
|
||||
"optional": true
|
||||
},
|
||||
"jotai-valtio": {
|
||||
"optional": true
|
||||
},
|
||||
"jotai-xstate": {
|
||||
"optional": true
|
||||
},
|
||||
"jotai-zustand": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/joycon": {
|
||||
"version": "3.1.1",
|
||||
"dev": true,
|
||||
@ -11063,6 +11229,22 @@
|
||||
"version": "3.2.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsondiffpatch": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz",
|
||||
"integrity": "sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^2.3.0",
|
||||
"diff-match-patch": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"jsondiffpatch": "bin/jsondiffpatch"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"dev": true,
|
||||
@ -12405,6 +12587,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.curry": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
||||
"integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"dev": true,
|
||||
@ -15549,6 +15737,23 @@
|
||||
"read": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prosemirror-changeset": {
|
||||
"version": "2.2.0",
|
||||
"license": "MIT",
|
||||
@ -15572,6 +15777,37 @@
|
||||
"prosemirror-transform": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-dev-tools": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-dev-tools/-/prosemirror-dev-tools-4.0.0.tgz",
|
||||
"integrity": "sha512-fF1AqJJa/ojVJTtc6WvygVQWFQI1zo37dewn0BQEKNLUfdJ4PRbqIYIFfh30wQhe8XcWw7J1ZidHZHz0a8VoYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.6",
|
||||
"@compiled/react": "^0.11.1",
|
||||
"html": "^1.0.0",
|
||||
"jotai": "^1.10.0",
|
||||
"jsondiffpatch": "^0.4.1",
|
||||
"nanoid": "^2.1.11",
|
||||
"prosemirror-model": ">=1.0.0",
|
||||
"prosemirror-state": ">=1.0.0",
|
||||
"react-dock": "^0.6.0",
|
||||
"react-json-tree": "^0.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-dev-tools/node_modules/nanoid": {
|
||||
"version": "2.1.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
|
||||
"integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prosemirror-dropcursor": {
|
||||
"version": "1.5.0",
|
||||
"license": "MIT",
|
||||
@ -15822,6 +16058,38 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-base16-styling": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz",
|
||||
"integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.7",
|
||||
"@types/base16": "^1.0.2",
|
||||
"@types/lodash": "^4.14.178",
|
||||
"base16": "^1.0.0",
|
||||
"color": "^3.2.1",
|
||||
"csstype": "^3.0.10",
|
||||
"lodash.curry": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dock": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dock/-/react-dock-0.6.0.tgz",
|
||||
"integrity": "sha512-jEOhv1s+pqRQ4JxgUw4XUotnprOehZ23mqchf3whxYXnvNgTQOXCxh6bpcqW8P6OybIk2bYO18r3qimZ3ypCbg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"license": "MIT",
|
||||
@ -15852,6 +16120,23 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-json-tree": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.17.0.tgz",
|
||||
"integrity": "sha512-hcWjibI/fAvsKnfYk+lka5OrE1Lvb1jH5pSnFhIU5T8cCCxB85r6h/NOzDPggSSgErjmx4rl3+2EkeclIKBOhg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-base16-styling": "^0.9.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.3.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.13.0",
|
||||
"dev": true,
|
||||
@ -16774,6 +17059,21 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/simplify-js": {
|
||||
"version": "1.2.4",
|
||||
"license": "BSD-2-Clause"
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { MarkType, NodeType, Schema } from '@tiptap/pm/model'
|
||||
import {
|
||||
MarkType, NodeType, Schema,
|
||||
} from '@tiptap/pm/model'
|
||||
import {
|
||||
EditorState, Plugin, PluginKey, Transaction,
|
||||
} from '@tiptap/pm/state'
|
||||
@ -16,6 +18,7 @@ import { getTextSerializersFromSchema } from './helpers/getTextSerializersFromSc
|
||||
import { isActive } from './helpers/isActive.js'
|
||||
import { isNodeEmpty } from './helpers/isNodeEmpty.js'
|
||||
import { resolveFocusPosition } from './helpers/resolveFocusPosition.js'
|
||||
import { NodePos } from './NodePos.js'
|
||||
import { style } from './style.js'
|
||||
import {
|
||||
CanCommands,
|
||||
@ -486,4 +489,22 @@ export class Editor extends EventEmitter<EditorEvents> {
|
||||
// @ts-ignore
|
||||
return !this.view?.docView
|
||||
}
|
||||
|
||||
public $node(selector: string, attributes?: { [key: string]: any }): NodePos | null {
|
||||
return this.$doc?.querySelector(selector, attributes) || null
|
||||
}
|
||||
|
||||
public $nodes(selector: string, attributes?: { [key: string]: any }): NodePos[] | null {
|
||||
return this.$doc?.querySelectorAll(selector, attributes) || null
|
||||
}
|
||||
|
||||
public $pos(pos: number) {
|
||||
const $pos = this.state.doc.resolve(pos)
|
||||
|
||||
return new NodePos($pos, this)
|
||||
}
|
||||
|
||||
get $doc() {
|
||||
return this.$pos(0)
|
||||
}
|
||||
}
|
||||
|
201
packages/core/src/NodePos.ts
Normal file
201
packages/core/src/NodePos.ts
Normal file
@ -0,0 +1,201 @@
|
||||
import {
|
||||
Fragment, Node, ResolvedPos,
|
||||
} from '@tiptap/pm/model'
|
||||
|
||||
import { Editor } from './Editor.js'
|
||||
import { Content, Range } from './types.js'
|
||||
|
||||
export class NodePos {
|
||||
private resolvedPos: ResolvedPos
|
||||
|
||||
private editor: Editor
|
||||
|
||||
constructor(pos: ResolvedPos, editor: Editor) {
|
||||
this.resolvedPos = pos
|
||||
this.editor = editor
|
||||
}
|
||||
|
||||
get node(): Node {
|
||||
return this.resolvedPos.node()
|
||||
}
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this.editor.view.domAtPos(this.pos).node as HTMLElement
|
||||
}
|
||||
|
||||
get depth(): number {
|
||||
return this.resolvedPos.depth
|
||||
}
|
||||
|
||||
get pos(): number {
|
||||
return this.resolvedPos.pos
|
||||
}
|
||||
|
||||
get content(): Fragment {
|
||||
return this.node.content
|
||||
}
|
||||
|
||||
set content(content: Content) {
|
||||
this.editor.commands.insertContentAt({ from: this.from, to: this.to }, content)
|
||||
}
|
||||
|
||||
get attributes() : { [key: string]: any } {
|
||||
return this.node.attrs
|
||||
}
|
||||
|
||||
get textContent(): string {
|
||||
return this.node.textContent
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.node.nodeSize
|
||||
}
|
||||
|
||||
get from(): number {
|
||||
return this.resolvedPos.start(this.resolvedPos.depth)
|
||||
}
|
||||
|
||||
get range(): Range {
|
||||
return {
|
||||
from: this.from,
|
||||
to: this.to,
|
||||
}
|
||||
}
|
||||
|
||||
get to(): number {
|
||||
return this.resolvedPos.end(this.resolvedPos.depth) + (this.node.isText ? 0 : 1)
|
||||
}
|
||||
|
||||
get parent(): NodePos | null {
|
||||
if (this.depth === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const parentPos = this.resolvedPos.start(this.resolvedPos.depth - 1)
|
||||
const $pos = this.resolvedPos.doc.resolve(parentPos)
|
||||
|
||||
return new NodePos($pos, this.editor)
|
||||
}
|
||||
|
||||
get before(): NodePos | null {
|
||||
let $pos = this.resolvedPos.doc.resolve(this.from - 2)
|
||||
|
||||
if ($pos.depth !== this.depth) {
|
||||
$pos = this.resolvedPos.doc.resolve(this.from - 3)
|
||||
}
|
||||
|
||||
return new NodePos($pos, this.editor)
|
||||
}
|
||||
|
||||
get after(): NodePos | null {
|
||||
let $pos = this.resolvedPos.doc.resolve(this.to + 2)
|
||||
|
||||
if ($pos.depth !== this.depth) {
|
||||
$pos = this.resolvedPos.doc.resolve(this.to + 3)
|
||||
}
|
||||
|
||||
return new NodePos($pos, this.editor)
|
||||
}
|
||||
|
||||
get children(): NodePos[] {
|
||||
const children: NodePos[] = []
|
||||
|
||||
this.node.content.forEach((node, offset) => {
|
||||
const targetPos = this.pos + offset + 1
|
||||
const $pos = this.resolvedPos.doc.resolve(targetPos)
|
||||
|
||||
if ($pos.depth === this.depth) {
|
||||
return
|
||||
}
|
||||
|
||||
children.push(new NodePos($pos, this.editor))
|
||||
})
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
get firstChild(): NodePos | null {
|
||||
return this.children[0] || null
|
||||
}
|
||||
|
||||
get lastChild(): NodePos | null {
|
||||
const children = this.children
|
||||
|
||||
return children[children.length - 1] || null
|
||||
}
|
||||
|
||||
closest(selector: string, attributes: { [key: string]: any } = {}): NodePos | null {
|
||||
let node: NodePos | null = null
|
||||
let currentNode = this.parent
|
||||
|
||||
while (currentNode && !node) {
|
||||
if (currentNode.node.type.name === selector) {
|
||||
if (Object.keys(attributes).length > 0) {
|
||||
const nodeAttributes = currentNode.node.attrs
|
||||
const attrKeys = Object.keys(attributes)
|
||||
|
||||
for (let index = 0; index < attrKeys.length; index += 1) {
|
||||
const key = attrKeys[index]
|
||||
|
||||
if (nodeAttributes[key] !== attributes[key]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node = currentNode
|
||||
}
|
||||
}
|
||||
|
||||
currentNode = currentNode.parent
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
querySelector(selector: string, attributes: { [key: string]: any } = {}): NodePos | null {
|
||||
return this.querySelectorAll(selector, attributes, true)[0] || null
|
||||
}
|
||||
|
||||
querySelectorAll(selector: string, attributes: { [key: string]: any } = {}, firstItemOnly = false): NodePos[] {
|
||||
let nodes: NodePos[] = []
|
||||
|
||||
// iterate through children recursively finding all nodes which match the selector with the node name
|
||||
if (!this.children || this.children.length === 0) {
|
||||
return nodes
|
||||
}
|
||||
|
||||
this.children.forEach(node => {
|
||||
if (node.node.type.name === selector) {
|
||||
if (Object.keys(attributes).length > 0) {
|
||||
const nodeAttributes = node.node.attrs
|
||||
const attrKeys = Object.keys(attributes)
|
||||
|
||||
for (let index = 0; index < attrKeys.length; index += 1) {
|
||||
const key = attrKeys[index]
|
||||
|
||||
if (nodeAttributes[key] !== attributes[key]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes.push(node)
|
||||
|
||||
if (firstItemOnly) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
nodes = nodes.concat(node.querySelectorAll(selector))
|
||||
})
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
setAttribute(attributes: { [key: string]: any }) {
|
||||
const oldSelection = this.editor.state.selection
|
||||
|
||||
this.editor.chain().setTextSelection(this.from).updateAttributes(this.node.type.name, attributes).setTextSelection(oldSelection.from)
|
||||
.run()
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ export * from './InputRule.js'
|
||||
export * from './inputRules/index.js'
|
||||
export * from './Mark.js'
|
||||
export * from './Node.js'
|
||||
export * from './NodePos.js'
|
||||
export * from './NodeView.js'
|
||||
export * from './PasteRule.js'
|
||||
export * from './pasteRules/index.js'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Table } from './table.js'
|
||||
|
||||
export * from './table.js'
|
||||
export * from './utilities/createTable.js'
|
||||
export * from './utilities/createColGroup.js'
|
||||
export * from './utilities/createTable.js'
|
||||
|
||||
export default Table
|
||||
|
Loading…
Reference in New Issue
Block a user