Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into typing-2
Browse files Browse the repository at this point in the history
  • Loading branch information
sciyoshi committed Nov 15, 2023
2 parents 3edecdf + 8b3c7e5 commit 5f51e6a
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 14 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This package provides Python implementations of the following
[ProseMirror](https://prosemirror.net/) packages:

- [`prosemirror-model`](https://github.com/ProseMirror/prosemirror-model) version 1.18.1
- [`prosemirror-transform`](https://github.com/ProseMirror/prosemirror-transform) version 1.7.1
- [`prosemirror-transform`](https://github.com/ProseMirror/prosemirror-transform) version 1.8.0
- [`prosemirror-test-builder`](https://github.com/ProseMirror/prosemirror-test-builder)
- [`prosemirror-schema-basic`](https://github.com/ProseMirror/prosemirror-schema-basic) version 1.1.2
- [`prosemirror-schema-list`](https://github.com/ProseMirror/prosemirror-schema-list)
Expand Down
6 changes: 3 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion prosemirror/test_builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,20 @@

from .build import builders

nodes = add_list_nodes(_schema.spec["nodes"], "paragraph block*", "block")

nodes.update(
{
"doc": {
"content": "block+",
"attrs": {"meta": {"default": None}},
}
}
)

test_schema: Schema[Any, Any] = Schema(
{
"nodes": add_list_nodes(_schema.spec["nodes"], "paragraph block*", "block"),
"nodes": nodes,
"marks": _schema.spec["marks"],
}
)
Expand All @@ -19,6 +30,8 @@
test_schema,
{
"doc": {"nodeType": "doc"},
"docMetaOne": {"nodeType": "doc", "meta": 1},
"docMetaTwo": {"nodeType": "doc", "meta": 2},
"p": {"nodeType": "paragraph"},
"pre": {"nodeType": "code_block"},
"h1": {"nodeType": "heading", "level": 1},
Expand Down
53 changes: 53 additions & 0 deletions prosemirror/transform/doc_attr_step.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from typing import Any, Optional

from prosemirror.model import Node, Schema
from prosemirror.transform.map import Mappable
from prosemirror.utils import JSON, JSONDict

from .step import Step, StepMap, StepResult, step_json_id


class DocAttrStep(Step):
def __init__(self, attr: str, value: JSON):
super().__init__()
self.attr = attr
self.value = value

def apply(self, doc: Node) -> StepResult:
attrs = {}
for name in doc.attrs:
attrs[name] = doc.attrs[name]
attrs[self.attr] = self.value
updated = doc.type.create(attrs, doc.content, doc.marks)
return StepResult.ok(updated)

def get_map(self) -> StepMap:
return StepMap.empty

def invert(self, doc: Node) -> "DocAttrStep":
return DocAttrStep(self.attr, doc.attrs[self.attr])

def map(self, mapping: Mappable) -> Optional[Step]:
return self

def to_json(self) -> JSONDict:
json_data = {
"stepType": "docAttr",
"attr": self.attr,
"value": self.value,
}

return json_data

@staticmethod
def from_json(schema: Schema[Any, Any], json_data: JSONDict | str) -> "DocAttrStep":
if isinstance(json_data, str):
import json

json_data = json.loads(json_data)
if not isinstance(json_data["attr"], str):
raise ValueError("Invalid input for DocAttrStep.from_json")
return DocAttrStep(json_data["attr"], json_data["value"])


step_json_id("docAttr", DocAttrStep)
7 changes: 6 additions & 1 deletion prosemirror/transform/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,13 @@ def can_split(
if node.type.spec.get("isolating"):
return False
rest = node.content.cut_by_index(index, node.child_count)
after: dict[str, NodeType] | Node | None = None

if types_after and len(types_after) > i:
override_child = types_after[i + 1]
rest = rest.replace_child(
0, override_child["type"].create(override_child.get("attrs"))
)
after: dict[str, NodeType] | Node | None = None
if types_after and len(types_after) > i:
after = types_after[i]
if not after:
Expand Down
41 changes: 34 additions & 7 deletions prosemirror/transform/transform.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from typing import TypedDict

from prosemirror.model import (
Expand Down Expand Up @@ -29,6 +30,8 @@
from prosemirror.transform.replace import replace_step
from prosemirror.utils import Attrs

from .doc_attr_step import DocAttrStep


def defines_content(type: NodeType | MarkType) -> bool | None:
if isinstance(type, NodeType):
Expand Down Expand Up @@ -201,26 +204,45 @@ def clear_incompatible(
node = self.doc.node_at(pos)
assert match is not None
assert node is not None
del_steps = []
repl_steps = []
cur = pos + 1
for i in range(node.child_count):
child = node.child(i)
end = cur + child.node_size
assert match is not None
allowed = match.match_type(child.type)
if not allowed:
del_steps.append(ReplaceStep(cur, end, Slice.empty))
repl_steps.append(ReplaceStep(cur, end, Slice.empty))
else:
match = allowed
for j in range(len(child.marks)):
if not parent_type.allows_mark_type(child.marks[j].type):
self.step(RemoveMarkStep(cur, end, child.marks[j]))
if child.is_text and not parent_type.spec.get("code"):
newline = re.compile(r"\r?\n|\r")
slice = None
m = newline.search(child.text)
while m:
if slice is None:
slice = Slice(
Fragment.from_(
parent_type.schema.text(
" ", parent_type.allowed_marks(child.marks)
)
),
0,
0,
)
repl_steps.append(
ReplaceStep(cur + m.start(), cur + m.end(), slice)
)
m = newline.search(child.text, m.end())
cur = end
if not match.valid_end:
fill = match.fill_before(Fragment.empty, True)
assert fill is not None
self.replace(cur, cur, Slice(fill, 0, 0))
for item in reversed(del_steps):
for item in reversed(repl_steps):
self.step(item)
return self

Expand Down Expand Up @@ -303,11 +325,13 @@ def replace_range(self, from_: int, to: int, slice: Slice) -> "Transform":

d = preferred_depth - 1
while d >= 0:
type = left_nodes[d].type
def_ = defines_content(type)
if def_ and from__.node(preferred_target_index).type.name != type.name:
left_node = left_nodes[d]
def_ = defines_content(left_node.type)
if def_ and not left_node.same_markup(
from__.node(abs(preferred_target) - 1)
):
preferred_depth = d
elif def_ or not type.is_text_block:
elif def_ or not left_node.type.is_text_block:
break
d -= 1

Expand Down Expand Up @@ -547,6 +571,9 @@ def set_node_markup(
def set_node_attribute(self, pos: int, attr: str, value: str | int) -> "Transform":
return self.step(AttrStep(pos, attr, value))

def set_doc_attribute(self, attr: str, value):
return self.step(DocAttrStep(attr, value))

def add_node_mark(self, pos: int, mark: Mark) -> "Transform":
return self.step(AddNodeMarkStep(pos, mark))

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "prosemirror"
version = "0.3.5"
version = "0.3.7"
description = "Python implementation of core ProseMirror modules for collaborative editing"
readme = "README.md"
authors = ["Shen Li <[email protected]>"]
Expand Down
37 changes: 37 additions & 0 deletions tests/prosemirror_transform/tests/test_trans.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from prosemirror.transform import Transform, TransformError, find_wrapping, lift_target

doc = out["doc"]
docMetaOne = out["docMetaOne"]
docMetaTwo = out["docMetaTwo"]
blockquote = out["blockquote"]
pre = out["pre"]
h1 = out["h1"]
Expand Down Expand Up @@ -502,6 +504,18 @@ def test_wrap(doc, expect, type, attrs, test_transform):
"code_block",
None,
),
(
doc(p("<a>one", img(), "two", img(), "three")),
doc(pre("onetwothree")),
"code_block",
None,
),
(
doc(pre("<a>one\ntwo\nthree")),
doc(p("one two three")),
"paragraph",
None,
),
],
)
def test_set_block_type(doc, expect, node_type, attrs, test_transform):
Expand Down Expand Up @@ -556,6 +570,19 @@ def test_set_node_attribute(doc, expect, attr, value, test_transform):
test_transform(tr, expect)


@pytest.mark.parametrize(
"doc,expect,attr,value",
[
(doc(h1("foo")), docMetaOne(h1("foo")), "meta", 1),
(docMetaOne(h1("foo")), docMetaTwo(h1("foo")), "meta", 2),
(docMetaTwo(h1("foo")), doc(h1("foo")), "meta", None),
],
)
def test_set_doc_attribute(doc, expect, attr, value, test_transform):
tr = Transform(doc).set_doc_attribute(attr, value)
test_transform(tr, expect)


@pytest.mark.parametrize(
"doc,source,expect",
[
Expand Down Expand Up @@ -718,6 +745,16 @@ def test_set_node_attribute(doc, expect, attr, value, test_transform):
doc(blockquote(blockquote(blockquote(p("hi"))))).slice(3, 6, True),
doc(p("hi")),
),
(
doc(ul(li(p("list1"), blockquote(p("<a>"))))),
doc(blockquote(p("<a>one<b>"))),
doc(ul(li(p("list1"), blockquote(p("one"))))),
),
(
doc(ul(li(p("list1"), ul(li(p("list2"), blockquote(p("<a>"))))))),
doc(blockquote(p("<a>one<b>"))),
doc(ul(li(p("list1"), ul(li(p("list2"), blockquote(p("one"))))))),
),
],
)
def test_replace(doc, source, expect, test_transform):
Expand Down

0 comments on commit 5f51e6a

Please sign in to comment.