tiptap-python-utils 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tiptap_python_utils/__init__.py +85 -0
- tiptap_python_utils/codec/__init__.py +29 -0
- tiptap_python_utils/codec/json.py +116 -0
- tiptap_python_utils/content.py +174 -0
- tiptap_python_utils/contract/__init__.py +5 -0
- tiptap_python_utils/contract/key.py +14 -0
- tiptap_python_utils/contract/kind.py +14 -0
- tiptap_python_utils/contract/policy.py +52 -0
- tiptap_python_utils/edit/__init__.py +12 -0
- tiptap_python_utils/edit/commands.py +163 -0
- tiptap_python_utils/exceptions.py +5 -0
- tiptap_python_utils/model/__init__.py +345 -0
- tiptap_python_utils/py.typed +1 -0
- tiptap_python_utils/select/__init__.py +5 -0
- tiptap_python_utils/select/selection.py +72 -0
- tiptap_python_utils/shared/__init__.py +23 -0
- tiptap_python_utils/shared/service.py +147 -0
- tiptap_python_utils/tasks/__init__.py +5 -0
- tiptap_python_utils/tasks/query.py +18 -0
- tiptap_python_utils/text/__init__.py +5 -0
- tiptap_python_utils/text/extract.py +113 -0
- tiptap_python_utils/tree/__init__.py +5 -0
- tiptap_python_utils/tree/path.py +36 -0
- tiptap_python_utils/types.py +7 -0
- tiptap_python_utils/walk/__init__.py +5 -0
- tiptap_python_utils/walk/traversal.py +88 -0
- tiptap_python_utils-0.1.0.dist-info/METADATA +176 -0
- tiptap_python_utils-0.1.0.dist-info/RECORD +31 -0
- tiptap_python_utils-0.1.0.dist-info/WHEEL +5 -0
- tiptap_python_utils-0.1.0.dist-info/licenses/LICENSE +21 -0
- tiptap_python_utils-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Visible text extraction for TipTap content."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Iterable, Iterator, List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
from ..content import Content
|
|
9
|
+
from ..contract import kind
|
|
10
|
+
from ..model import CodeBlock, Heading, Node, Paragraph, TaskItem
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class NodeText:
|
|
15
|
+
node_id: str
|
|
16
|
+
text: str
|
|
17
|
+
html_tag: str = "p"
|
|
18
|
+
context: Optional[str] = None
|
|
19
|
+
node_type: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def visible_text(content: Content) -> str:
|
|
23
|
+
return content.text
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def word_count(content: Content) -> int:
|
|
27
|
+
return content.word_count()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def text_slices(content: Content, *, context: bool = False) -> List[NodeText]:
|
|
31
|
+
nodes = _content_nodes(content)
|
|
32
|
+
if context:
|
|
33
|
+
return list(_apply_heading_context(nodes))
|
|
34
|
+
return list(nodes)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _content_nodes(content: Content) -> Iterator[NodeText]:
|
|
38
|
+
if content.root is None:
|
|
39
|
+
return
|
|
40
|
+
for ref in content.refs(parseable=True):
|
|
41
|
+
node = ref.node
|
|
42
|
+
node_id = _node_id(node)
|
|
43
|
+
if not node_id or not isinstance(node, (Paragraph, Heading, TaskItem, CodeBlock)):
|
|
44
|
+
continue
|
|
45
|
+
yield NodeText(
|
|
46
|
+
node_id=node_id,
|
|
47
|
+
text=node.text,
|
|
48
|
+
html_tag=_tag(node),
|
|
49
|
+
node_type=node.kind,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _node_id(node: Node) -> str:
|
|
54
|
+
if isinstance(node, TaskItem):
|
|
55
|
+
return node.task_item_id
|
|
56
|
+
return node.id
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _tag(node: Node) -> str:
|
|
60
|
+
if isinstance(node, Heading):
|
|
61
|
+
return f"h{node.level}"
|
|
62
|
+
if node.kind == kind.TASK_ITEM:
|
|
63
|
+
return "li"
|
|
64
|
+
if node.kind == kind.CODE_BLOCK:
|
|
65
|
+
return "code"
|
|
66
|
+
return "p"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _apply_heading_context(nodes: Iterable[NodeText]) -> Iterator[NodeText]:
|
|
70
|
+
context_stack: List[Tuple[int, str]] = []
|
|
71
|
+
|
|
72
|
+
for node in nodes:
|
|
73
|
+
if not node.text.strip():
|
|
74
|
+
context_stack.clear()
|
|
75
|
+
yield node
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
if _is_heading(node.html_tag):
|
|
79
|
+
level = int(node.html_tag[1])
|
|
80
|
+
parent_context = _context_string(context_stack)
|
|
81
|
+
_push_heading(context_stack, level, node.text)
|
|
82
|
+
yield NodeText(
|
|
83
|
+
node_id=node.node_id,
|
|
84
|
+
text=node.text,
|
|
85
|
+
html_tag=node.html_tag,
|
|
86
|
+
context=parent_context,
|
|
87
|
+
node_type=node.node_type,
|
|
88
|
+
)
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
yield NodeText(
|
|
92
|
+
node_id=node.node_id,
|
|
93
|
+
text=node.text,
|
|
94
|
+
html_tag=node.html_tag,
|
|
95
|
+
context=_context_string(context_stack),
|
|
96
|
+
node_type=node.node_type,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _is_heading(html_tag: str) -> bool:
|
|
101
|
+
return len(html_tag) == 2 and html_tag[0] == "h" and html_tag[1].isdigit()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _push_heading(stack: List[Tuple[int, str]], level: int, text: str) -> None:
|
|
105
|
+
while stack and stack[-1][0] >= level:
|
|
106
|
+
stack.pop()
|
|
107
|
+
stack.append((level, text))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _context_string(stack: List[Tuple[int, str]]) -> Optional[str]:
|
|
111
|
+
if not stack:
|
|
112
|
+
return None
|
|
113
|
+
return " > ".join(text for _, text in stack)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Immutable tree path operations for typed TipTap nodes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
|
|
8
|
+
from ..exceptions import TiptapValidationError
|
|
9
|
+
|
|
10
|
+
from ..contract import key
|
|
11
|
+
from ..model import Node
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def node_at_path(node: Node, path: Tuple[int, ...]) -> Node:
|
|
15
|
+
current = node
|
|
16
|
+
for index in path:
|
|
17
|
+
try:
|
|
18
|
+
current = current.content[index]
|
|
19
|
+
except IndexError as exc:
|
|
20
|
+
raise TiptapValidationError(
|
|
21
|
+
"TipTap selection path is no longer valid"
|
|
22
|
+
) from exc
|
|
23
|
+
return current
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def replace_at_path(node: Node, path: Tuple[int, ...], replacement_node: Node) -> Node:
|
|
27
|
+
if not path:
|
|
28
|
+
return replacement_node
|
|
29
|
+
|
|
30
|
+
index = path[0]
|
|
31
|
+
content = list(node.content)
|
|
32
|
+
if index >= len(content):
|
|
33
|
+
raise TiptapValidationError("TipTap selection path is no longer valid")
|
|
34
|
+
|
|
35
|
+
content[index] = replace_at_path(content[index], path[1:], replacement_node)
|
|
36
|
+
return replace(node, content=tuple(content), present=node.present | {key.CONTENT})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Depth-first TipTap traversal."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Iterator, Optional, Tuple, Type, TypeVar
|
|
7
|
+
|
|
8
|
+
from ..contract import kind, policy
|
|
9
|
+
from ..model import CodeBlock, Heading, ListItem, Node, Paragraph, TaskItem
|
|
10
|
+
|
|
11
|
+
NodeT = TypeVar("NodeT", bound=Node)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class Ref:
|
|
16
|
+
node: Node
|
|
17
|
+
path: Tuple[int, ...]
|
|
18
|
+
parent_kind: str
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def node_id(self) -> Optional[str]:
|
|
22
|
+
return selection_id(self.node)
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def parseable(self) -> bool:
|
|
26
|
+
return not self.path or policy.is_parseable(self.node.kind, self.parent_kind)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Walker:
|
|
30
|
+
"""Depth-first traversal for typed TipTap content."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, root_or_content: Any) -> None:
|
|
33
|
+
self._root = getattr(root_or_content, "root", root_or_content)
|
|
34
|
+
|
|
35
|
+
def walk(self) -> Iterator[Node]:
|
|
36
|
+
if self._root is None:
|
|
37
|
+
return
|
|
38
|
+
yield from _walk(self._root)
|
|
39
|
+
|
|
40
|
+
def of_type(self, node_class: Type[NodeT]) -> list[NodeT]:
|
|
41
|
+
return [node for node in self.walk() if isinstance(node, node_class)]
|
|
42
|
+
|
|
43
|
+
def refs(self, *, parseable: bool = False) -> Iterator[Ref]:
|
|
44
|
+
if self._root is None:
|
|
45
|
+
return
|
|
46
|
+
yield from _refs(
|
|
47
|
+
self._root,
|
|
48
|
+
path=(),
|
|
49
|
+
parent_kind=kind.DOC,
|
|
50
|
+
parseable=parseable,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def selection_id(node: Node) -> Optional[str]:
|
|
55
|
+
attrs_id = policy.node_id(getattr(node, "attrs", {}))
|
|
56
|
+
if attrs_id:
|
|
57
|
+
return attrs_id
|
|
58
|
+
if isinstance(node, TaskItem):
|
|
59
|
+
return node.local_task_item_id or node.task_item_id or None
|
|
60
|
+
if isinstance(node, (Paragraph, Heading, ListItem, CodeBlock)):
|
|
61
|
+
return node.id or None
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _walk(node: Node) -> Iterator[Node]:
|
|
66
|
+
yield node
|
|
67
|
+
for child in node.content:
|
|
68
|
+
yield from _walk(child)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _refs(
|
|
72
|
+
node: Node,
|
|
73
|
+
*,
|
|
74
|
+
path: tuple[int, ...],
|
|
75
|
+
parent_kind: str,
|
|
76
|
+
parseable: bool,
|
|
77
|
+
) -> Iterator[Ref]:
|
|
78
|
+
ref = Ref(node=node, path=path, parent_kind=parent_kind)
|
|
79
|
+
if not parseable or ref.parseable:
|
|
80
|
+
yield ref
|
|
81
|
+
|
|
82
|
+
for index, child in enumerate(node.content):
|
|
83
|
+
yield from _refs(
|
|
84
|
+
child,
|
|
85
|
+
path=path + (index,),
|
|
86
|
+
parent_kind=node.kind,
|
|
87
|
+
parseable=parseable,
|
|
88
|
+
)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tiptap_python_utils
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python utilities for parsing, traversing, editing, and serializing TipTap JSON content.
|
|
5
|
+
Author: tiptap_python_utils contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 tiptap_python_utils contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/tugkanpilka/tiptap-python-utils
|
|
29
|
+
Project-URL: Repository, https://github.com/tugkanpilka/tiptap-python-utils
|
|
30
|
+
Project-URL: Issues, https://github.com/tugkanpilka/tiptap-python-utils/issues
|
|
31
|
+
Project-URL: Changelog, https://github.com/tugkanpilka/tiptap-python-utils/blob/main/CHANGELOG.md
|
|
32
|
+
Keywords: tiptap,prosemirror,json,ast,editor
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Typing :: Typed
|
|
42
|
+
Requires-Python: >=3.9
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
48
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# tiptap_python_utils
|
|
52
|
+
|
|
53
|
+
Python utilities for TipTap JSON content.
|
|
54
|
+
|
|
55
|
+
`tiptap_python_utils` parses TipTap documents into typed Python nodes, preserves
|
|
56
|
+
unknown/custom nodes for lossless round trips, and provides small helpers for
|
|
57
|
+
traversal, immutable edits, visible text extraction, task queries, and shared
|
|
58
|
+
node synchronization.
|
|
59
|
+
|
|
60
|
+
The package has no runtime dependencies.
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install tiptap_python_utils
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from tiptap_python_utils import Content, Paragraph, Text, kind
|
|
72
|
+
|
|
73
|
+
raw = {
|
|
74
|
+
"type": "doc",
|
|
75
|
+
"content": [
|
|
76
|
+
{
|
|
77
|
+
"type": "paragraph",
|
|
78
|
+
"attrs": {"id": "p1"},
|
|
79
|
+
"content": [{"type": "text", "text": "Old"}],
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
updated = Content.require(raw).where_id("p1").text("New").dump()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Typed Nodes
|
|
88
|
+
|
|
89
|
+
Build typed nodes directly and serialize them back to TipTap-compatible JSON:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
node = Paragraph(id="p1", content=(Text(value="Hello"),))
|
|
93
|
+
doc = Content.wrap(node.raw())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Unknown/custom node types are preserved as `Unknown` nodes and round-trip without
|
|
97
|
+
dropping extra fields.
|
|
98
|
+
|
|
99
|
+
## Selection And Editing
|
|
100
|
+
|
|
101
|
+
Select nodes by id or TipTap kind:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
updated = Content.require(raw).of(kind.PARAGRAPH).attr("color", "blue").dump()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Selection methods return updated immutable content:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
updated = (
|
|
111
|
+
Content.require(raw)
|
|
112
|
+
.where_id("p1")
|
|
113
|
+
.text("Updated")
|
|
114
|
+
.attr("data-state", "reviewed")
|
|
115
|
+
.dump()
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Text Extraction
|
|
120
|
+
|
|
121
|
+
Extract visible text or contextual slices:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from tiptap_python_utils import Content, text_slices, visible_text, word_count
|
|
125
|
+
|
|
126
|
+
content = Content.require(raw)
|
|
127
|
+
|
|
128
|
+
plain_text = visible_text(content)
|
|
129
|
+
count = word_count(content)
|
|
130
|
+
slices = text_slices(content, context=True)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Tasks
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from tiptap_python_utils import Content, has_open_tasks, open_tasks
|
|
137
|
+
|
|
138
|
+
content = Content.require(raw)
|
|
139
|
+
|
|
140
|
+
pending = has_open_tasks(content)
|
|
141
|
+
items = open_tasks(content)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Public API
|
|
145
|
+
|
|
146
|
+
Common imports are available from the package root:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from tiptap_python_utils import (
|
|
150
|
+
Content,
|
|
151
|
+
Paragraph,
|
|
152
|
+
TaskItem,
|
|
153
|
+
Text,
|
|
154
|
+
append_node,
|
|
155
|
+
has_open_tasks,
|
|
156
|
+
kind,
|
|
157
|
+
replace_node,
|
|
158
|
+
shared_families,
|
|
159
|
+
sync_shared,
|
|
160
|
+
text_slices,
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
python -m pip install -e ".[dev]"
|
|
168
|
+
pytest -q
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Build and check a release artifact:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
python -m build
|
|
175
|
+
python -m twine check dist/*
|
|
176
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
tiptap_python_utils/__init__.py,sha256=3Zz8DTxEaQxeLqhLVpwXhOaVV1SAKq_2JgwSXbBKySA,1673
|
|
2
|
+
tiptap_python_utils/content.py,sha256=KP-M4lDt7lBsQJTI137pnPmb2fthi2YBD2BgmHfvq1k,4937
|
|
3
|
+
tiptap_python_utils/exceptions.py,sha256=GIP91_6gaz4Vp-uMbmZBFFpnHHvToKYWyYf78Apt0ns,150
|
|
4
|
+
tiptap_python_utils/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
5
|
+
tiptap_python_utils/types.py,sha256=f6ocw9oOTyj_khush3bP5cHS2YpybKS0jQgjzSNVx_k,152
|
|
6
|
+
tiptap_python_utils/codec/__init__.py,sha256=Ip9rhIR2-1R_See3kU3xpATiEgSpz5eZfbQK3OrJjrM,442
|
|
7
|
+
tiptap_python_utils/codec/json.py,sha256=RIxKbG88vGYS57IylJqwNjpNjOLrb8mmkEKcNTA69IY,3176
|
|
8
|
+
tiptap_python_utils/contract/__init__.py,sha256=gJMHPqIl6vkcXAOKNKtZIbs8aQs1abqykEpc5i3abQY,115
|
|
9
|
+
tiptap_python_utils/contract/key.py,sha256=6T4Y-26hESduXmLaNOOpbZ-zTYkHgABvDCHjL6b2VDM,260
|
|
10
|
+
tiptap_python_utils/contract/kind.py,sha256=I-vkJ9mskw07qjbIR5uC1s3z0AvtwA2y3qovkzTsY2Q,297
|
|
11
|
+
tiptap_python_utils/contract/policy.py,sha256=AeJfLv07JWqhuJMakUgz1Y-bMpac28h426bj17hKvwk,1193
|
|
12
|
+
tiptap_python_utils/edit/__init__.py,sha256=8d8K5_v2ka8TnvNghwuyTwBb99OJTIHNyx_i8uHg3cw,251
|
|
13
|
+
tiptap_python_utils/edit/commands.py,sha256=rifp_43vB_ICyI4McYeBmpY3xoY5sB_RFxNOzFbwQAA,5545
|
|
14
|
+
tiptap_python_utils/model/__init__.py,sha256=3m5vTlc2cZTU0atV6xWFvU38bxVjI-xYOR3bQtqEyTM,10320
|
|
15
|
+
tiptap_python_utils/select/__init__.py,sha256=lFavos0E3w2D_keUPDa5xkWhIjkQiee7yNK7X-G-U6o,88
|
|
16
|
+
tiptap_python_utils/select/selection.py,sha256=DWfdGLhMF8HDrBJ1Jiv8XVnPXuexzce7JhzAflynTdw,2409
|
|
17
|
+
tiptap_python_utils/shared/__init__.py,sha256=DL09HcsabBxFXBcvnd7hH3FLT3sR9EZkjurPPQM8U5Q,402
|
|
18
|
+
tiptap_python_utils/shared/service.py,sha256=sWIL32sOGZo8ydzzTGghRTh56-EQGlrZgmEPaxPcIFk,4679
|
|
19
|
+
tiptap_python_utils/tasks/__init__.py,sha256=EXNu9YfJHBjyUZGf5HJIvaW7Ub_aN6fw_CZBNjBW0Og,154
|
|
20
|
+
tiptap_python_utils/tasks/query.py,sha256=59hb0fubBHlfOQekv_KY3DbTEBPFdYh_1Tc9LuFsL9w,464
|
|
21
|
+
tiptap_python_utils/text/__init__.py,sha256=F8PVtbGQlshGAtNLGNhGt8eyOwyE3Z5GC2lZLovxWg4,170
|
|
22
|
+
tiptap_python_utils/text/extract.py,sha256=aluDO3Hp0M7cyLoTtpZAeWHECF5Gh8ApZFdgs_se1aA,3046
|
|
23
|
+
tiptap_python_utils/tree/__init__.py,sha256=YzlXRkQrlRGYmkWbpZ8mutZOmK4BzmiZaBqUtFv8c8c,146
|
|
24
|
+
tiptap_python_utils/tree/path.py,sha256=bHmBFIZ3BV4FVlB79HhSgjeP6qHdNXClB9DFovSe8fs,1067
|
|
25
|
+
tiptap_python_utils/walk/__init__.py,sha256=hKU7DhRY6HVlxCxyWmnZtoobWNx5MCIT0OoErGe4Bp4,120
|
|
26
|
+
tiptap_python_utils/walk/traversal.py,sha256=6GDFmPAuo_t4FeOeV6hqrqvwcTZ8G93ep21-hrFt7S0,2320
|
|
27
|
+
tiptap_python_utils-0.1.0.dist-info/licenses/LICENSE,sha256=pIGeAFdiaTJBpJilmEbR8YQ5agrt7DqbGlTTrzqO6Qo,1089
|
|
28
|
+
tiptap_python_utils-0.1.0.dist-info/METADATA,sha256=JNf4QQALy1Cw400CNm65_NiXfhEFcqyqgUHAIMd1I9k,4895
|
|
29
|
+
tiptap_python_utils-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
30
|
+
tiptap_python_utils-0.1.0.dist-info/top_level.txt,sha256=t7g66MmK6WqTixs5_ZnXe4j-RvH9thuDRKvxk_8F0AQ,20
|
|
31
|
+
tiptap_python_utils-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tiptap_python_utils contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tiptap_python_utils
|