prosereflect 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +63 -0
- data/.github/workflows/links.yml +97 -0
- data/.gitignore +4 -0
- data/.rubocop_todo.yml +61 -75
- data/README.adoc +2 -0
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +45 -0
- data/docs/_advanced/index.adoc +15 -0
- data/docs/_advanced/schema.adoc +112 -0
- data/docs/_advanced/step-map.adoc +66 -0
- data/docs/_advanced/steps.adoc +88 -0
- data/docs/_advanced/test-builder.adoc +61 -0
- data/docs/_advanced/transform.adoc +92 -0
- data/docs/_config.yml +174 -0
- data/docs/_features/html-input.adoc +69 -0
- data/docs/_features/html-output.adoc +45 -0
- data/docs/_features/index.adoc +15 -0
- data/docs/_features/marks.adoc +86 -0
- data/docs/_features/node-types.adoc +124 -0
- data/docs/_features/user-mentions.adoc +47 -0
- data/docs/_guides/custom-nodes.adoc +107 -0
- data/docs/_guides/index.adoc +13 -0
- data/docs/_guides/round-trip-html.adoc +91 -0
- data/docs/_guides/serialization.adoc +109 -0
- data/docs/_pages/index.adoc +67 -0
- data/docs/_reference/document-api.adoc +49 -0
- data/docs/_reference/index.adoc +14 -0
- data/docs/_reference/node-api.adoc +79 -0
- data/docs/_reference/schema-api.adoc +95 -0
- data/docs/_reference/transform-api.adoc +77 -0
- data/docs/_understanding/document-model.adoc +65 -0
- data/docs/_understanding/fragment.adoc +52 -0
- data/docs/_understanding/index.adoc +14 -0
- data/docs/_understanding/resolved-position.adoc +53 -0
- data/docs/_understanding/slice.adoc +54 -0
- data/docs/lychee.toml +63 -0
- data/lib/prosereflect/blockquote.rb +9 -0
- data/lib/prosereflect/bullet_list.rb +25 -19
- data/lib/prosereflect/code_block.rb +1 -5
- data/lib/prosereflect/fragment.rb +249 -0
- data/lib/prosereflect/horizontal_rule.rb +9 -0
- data/lib/prosereflect/image.rb +9 -0
- data/lib/prosereflect/input/html.rb +96 -0
- data/lib/prosereflect/node.rb +141 -3
- data/lib/prosereflect/ordered_list.rb +2 -0
- data/lib/prosereflect/output/html.rb +227 -0
- data/lib/prosereflect/parser.rb +9 -0
- data/lib/prosereflect/resolved_pos.rb +256 -0
- data/lib/prosereflect/schema/attribute.rb +57 -0
- data/lib/prosereflect/schema/content_match.rb +656 -0
- data/lib/prosereflect/schema/fragment.rb +166 -0
- data/lib/prosereflect/schema/mark.rb +121 -0
- data/lib/prosereflect/schema/mark_type.rb +130 -0
- data/lib/prosereflect/schema/node.rb +236 -0
- data/lib/prosereflect/schema/node_type.rb +274 -0
- data/lib/prosereflect/schema/schema_main.rb +190 -0
- data/lib/prosereflect/schema/spec.rb +92 -0
- data/lib/prosereflect/schema.rb +39 -0
- data/lib/prosereflect/text.rb +24 -0
- data/lib/prosereflect/transform/attr_step.rb +157 -0
- data/lib/prosereflect/transform/insert_step.rb +115 -0
- data/lib/prosereflect/transform/mapping.rb +82 -0
- data/lib/prosereflect/transform/mark_step.rb +269 -0
- data/lib/prosereflect/transform/replace_around_step.rb +181 -0
- data/lib/prosereflect/transform/replace_step.rb +157 -0
- data/lib/prosereflect/transform/slice.rb +91 -0
- data/lib/prosereflect/transform/step.rb +89 -0
- data/lib/prosereflect/transform/step_map.rb +126 -0
- data/lib/prosereflect/transform/structure.rb +120 -0
- data/lib/prosereflect/transform/transform.rb +341 -0
- data/lib/prosereflect/transform.rb +26 -0
- data/lib/prosereflect/version.rb +1 -1
- data/lib/prosereflect.rb +3 -0
- data/spec/fixtures/documents/formatted_text.yaml +14 -0
- data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
- data/spec/fixtures/documents/lists_doc.yaml +32 -0
- data/spec/fixtures/documents/mixed_content.yaml +40 -0
- data/spec/fixtures/documents/nested_doc.yaml +20 -0
- data/spec/fixtures/documents/simple_doc.yaml +6 -0
- data/spec/fixtures/documents/table_doc.yaml +32 -0
- data/spec/fixtures/documents/transform_test.yaml +14 -0
- data/spec/fixtures/schema/custom_schema.rb +37 -0
- data/spec/fixtures/schema/test_schema.rb +46 -0
- data/spec/fixtures/test_builder/helpers.rb +212 -0
- data/spec/prosereflect/document_spec.rb +1 -1
- data/spec/prosereflect/fragment_spec.rb +273 -0
- data/spec/prosereflect/input/html_spec.rb +197 -1
- data/spec/prosereflect/node_spec.rb +128 -0
- data/spec/prosereflect/output/whitespace_spec.rb +248 -0
- data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
- data/spec/prosereflect/resolved_pos_spec.rb +74 -0
- data/spec/prosereflect/schema/conftest.rb +68 -0
- data/spec/prosereflect/schema/content_match_spec.rb +237 -0
- data/spec/prosereflect/schema/mark_spec.rb +274 -0
- data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
- data/spec/prosereflect/schema/node_type_spec.rb +142 -0
- data/spec/prosereflect/schema/schema_spec.rb +194 -0
- data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
- data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
- data/spec/prosereflect/transform/mapping_spec.rb +226 -0
- data/spec/prosereflect/transform/replace_spec.rb +832 -0
- data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
- data/spec/prosereflect/transform/slice_spec.rb +48 -0
- data/spec/prosereflect/transform/step_map_spec.rb +70 -0
- data/spec/prosereflect/transform/step_spec.rb +211 -0
- data/spec/prosereflect/transform/structure_spec.rb +98 -0
- data/spec/prosereflect/transform/transform_spec.rb +238 -0
- data/spec/spec_helper.rb +1 -0
- metadata +90 -2
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Node Types
|
|
4
|
+
parent: Features
|
|
5
|
+
nav_order: 1
|
|
6
|
+
---
|
|
7
|
+
= Node Types
|
|
8
|
+
|
|
9
|
+
prosereflect provides node classes for all standard ProseMirror node types. Each inherits from `Prosereflect::Node` and defines a `PM_TYPE` constant.
|
|
10
|
+
|
|
11
|
+
== Block Nodes
|
|
12
|
+
|
|
13
|
+
=== Document
|
|
14
|
+
|
|
15
|
+
Top-level container. Create with `Document.create`:
|
|
16
|
+
|
|
17
|
+
[source,ruby]
|
|
18
|
+
----
|
|
19
|
+
doc = Prosereflect::Document.create
|
|
20
|
+
doc.add_paragraph("Hello world")
|
|
21
|
+
----
|
|
22
|
+
|
|
23
|
+
Convenience methods: `tables`, `paragraphs`, `find_first`, `find_all`, `find_children`, `add_paragraph`, `add_heading`, `add_table`, `add_image`, `add_bullet_list`, `add_ordered_list`, `add_blockquote`, `add_horizontal_rule`, `add_code_block_wrapper`, `add_user`.
|
|
24
|
+
|
|
25
|
+
=== Paragraph
|
|
26
|
+
|
|
27
|
+
Represents a paragraph of text.
|
|
28
|
+
|
|
29
|
+
[source,ruby]
|
|
30
|
+
----
|
|
31
|
+
para = Prosereflect::Paragraph.new
|
|
32
|
+
para.add_text("Hello")
|
|
33
|
+
para.text_content # => "Hello"
|
|
34
|
+
----
|
|
35
|
+
|
|
36
|
+
=== Heading
|
|
37
|
+
|
|
38
|
+
Heading with level attribute (1-6):
|
|
39
|
+
|
|
40
|
+
[source,ruby]
|
|
41
|
+
----
|
|
42
|
+
heading = Prosereflect::Heading.new
|
|
43
|
+
heading.level = 2
|
|
44
|
+
heading.add_text("Title")
|
|
45
|
+
----
|
|
46
|
+
|
|
47
|
+
=== Table, TableRow, TableCell, TableHeader
|
|
48
|
+
|
|
49
|
+
Table structures with convenience methods:
|
|
50
|
+
|
|
51
|
+
[source,ruby]
|
|
52
|
+
----
|
|
53
|
+
table = doc.add_table
|
|
54
|
+
row = table.add_row
|
|
55
|
+
cell = row.add_cell
|
|
56
|
+
cell.add_paragraph("Data")
|
|
57
|
+
----
|
|
58
|
+
|
|
59
|
+
Table methods: `header_row`, `data_rows`, `cell_at(row, col)`, `rows`.
|
|
60
|
+
|
|
61
|
+
=== Lists
|
|
62
|
+
|
|
63
|
+
`BulletList` and `OrderedList` contain `ListItem` children:
|
|
64
|
+
|
|
65
|
+
[source,ruby]
|
|
66
|
+
----
|
|
67
|
+
list = doc.add_bullet_list
|
|
68
|
+
item = list.add_item
|
|
69
|
+
item.add_paragraph("First item")
|
|
70
|
+
----
|
|
71
|
+
|
|
72
|
+
`OrderedList` supports a `start` attribute for numbering.
|
|
73
|
+
|
|
74
|
+
=== Blockquote
|
|
75
|
+
|
|
76
|
+
Contains block-level children:
|
|
77
|
+
|
|
78
|
+
[source,ruby]
|
|
79
|
+
----
|
|
80
|
+
quote = doc.add_blockquote
|
|
81
|
+
quote.add_paragraph("To be or not to be")
|
|
82
|
+
----
|
|
83
|
+
|
|
84
|
+
=== CodeBlockWrapper and CodeBlock
|
|
85
|
+
|
|
86
|
+
Code blocks with syntax highlighting:
|
|
87
|
+
|
|
88
|
+
[source,ruby]
|
|
89
|
+
----
|
|
90
|
+
wrapper = doc.add_code_block_wrapper
|
|
91
|
+
block = wrapper.add_code_block("puts 'hello'", language: "ruby")
|
|
92
|
+
----
|
|
93
|
+
|
|
94
|
+
=== Image
|
|
95
|
+
|
|
96
|
+
Inline image node with `src`, `alt`, `title`, `width`, `height` attributes.
|
|
97
|
+
|
|
98
|
+
=== HorizontalRule
|
|
99
|
+
|
|
100
|
+
Horizontal rule element with `style`, `width`, `thickness` attributes.
|
|
101
|
+
|
|
102
|
+
=== HardBreak
|
|
103
|
+
|
|
104
|
+
Inline break element within paragraphs.
|
|
105
|
+
|
|
106
|
+
== Text Nodes
|
|
107
|
+
|
|
108
|
+
`Prosereflect::Text` represents a text node with `text` attribute and optional marks. `node_size` returns `text.length + 1`.
|
|
109
|
+
|
|
110
|
+
== Common Methods
|
|
111
|
+
|
|
112
|
+
All node types inherit from `Node`:
|
|
113
|
+
|
|
114
|
+
* `node_size` -- size in the document position space
|
|
115
|
+
* `text?` -- whether this is a text node
|
|
116
|
+
* `cut(from, to)` -- return a copy with content restricted to range
|
|
117
|
+
* `copy(content)` -- return a copy with different content
|
|
118
|
+
* `find_first(type)`, `find_all(type)`, `find_children(klass)` -- node traversal
|
|
119
|
+
* `add_child(node)` -- append a child
|
|
120
|
+
* `text_content` -- combined text of all descendant text nodes
|
|
121
|
+
* `to_h` -- serialize to ProseMirror JSON hash
|
|
122
|
+
* `resolve(pos)` -- resolve a position to a `ResolvedPos`
|
|
123
|
+
* `nodes_between(from, to)` -- iterate nodes in a range
|
|
124
|
+
* `descendants` -- iterate all descendant nodes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: User Mentions
|
|
4
|
+
parent: Features
|
|
5
|
+
nav_order: 5
|
|
6
|
+
---
|
|
7
|
+
= User Mentions
|
|
8
|
+
|
|
9
|
+
prosereflect supports user @mentions as a special inline node type.
|
|
10
|
+
|
|
11
|
+
== Creating Mentions
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
doc = Prosereflect::Document.create
|
|
16
|
+
para = doc.add_paragraph("Hello ")
|
|
17
|
+
user = Prosereflect::User.new
|
|
18
|
+
user.id = "123"
|
|
19
|
+
para.add_child(user)
|
|
20
|
+
para.add_text("!")
|
|
21
|
+
----
|
|
22
|
+
|
|
23
|
+
== HTML Representation
|
|
24
|
+
|
|
25
|
+
User mentions are represented as `<user-mention>` elements in HTML:
|
|
26
|
+
|
|
27
|
+
[source,ruby]
|
|
28
|
+
----
|
|
29
|
+
html = Prosereflect::Output::Html.convert(doc)
|
|
30
|
+
# includes: <user-mention data-id="123"></user-mention>
|
|
31
|
+
----
|
|
32
|
+
|
|
33
|
+
== Parsing from HTML
|
|
34
|
+
|
|
35
|
+
[source,ruby]
|
|
36
|
+
----
|
|
37
|
+
html = '<p>Hello <user-mention data-id="123"></user-mention>!</p>'
|
|
38
|
+
doc = Prosereflect::Input::Html.parse(html)
|
|
39
|
+
users = doc.find_all('user')
|
|
40
|
+
users.first.id # => "123"
|
|
41
|
+
----
|
|
42
|
+
|
|
43
|
+
== Use Cases
|
|
44
|
+
|
|
45
|
+
* Mentioning users in comments or messages
|
|
46
|
+
* Tagging users in collaborative documents
|
|
47
|
+
* Tracking user references in content
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Custom Nodes
|
|
4
|
+
parent: Guides
|
|
5
|
+
nav_order: 2
|
|
6
|
+
---
|
|
7
|
+
= Custom Nodes
|
|
8
|
+
|
|
9
|
+
This guide explains how to define custom node types that integrate with prosereflect's serialization and HTML conversion systems.
|
|
10
|
+
|
|
11
|
+
== Defining a Custom Node
|
|
12
|
+
|
|
13
|
+
Create a new class that inherits from `Prosereflect::Node`:
|
|
14
|
+
|
|
15
|
+
[source,ruby]
|
|
16
|
+
----
|
|
17
|
+
class Callout < Prosereflect::Node
|
|
18
|
+
PM_TYPE = "callout"
|
|
19
|
+
|
|
20
|
+
# Define custom attributes
|
|
21
|
+
attribute :variant, :string
|
|
22
|
+
|
|
23
|
+
# Define serialization mapping
|
|
24
|
+
key_value do
|
|
25
|
+
map "type", to: :type, render_default: true
|
|
26
|
+
map "attrs", to: :attrs
|
|
27
|
+
map "content", to: :content
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
----
|
|
31
|
+
|
|
32
|
+
== Key Concepts
|
|
33
|
+
|
|
34
|
+
=== PM_TYPE
|
|
35
|
+
|
|
36
|
+
The `PM_TYPE` constant must match the ProseMirror type string used in JSON serialization. This is how the parser maps JSON data to your Ruby class.
|
|
37
|
+
|
|
38
|
+
=== Attributes
|
|
39
|
+
|
|
40
|
+
Use `lutaml-model`'s `attribute` DSL to define custom attributes:
|
|
41
|
+
|
|
42
|
+
[source,ruby]
|
|
43
|
+
----
|
|
44
|
+
attribute :variant, :string # string attribute
|
|
45
|
+
attribute :count, :integer # integer attribute
|
|
46
|
+
----
|
|
47
|
+
|
|
48
|
+
Attributes are automatically included in `to_h` output when present in the `attrs` hash.
|
|
49
|
+
|
|
50
|
+
=== key_value Block
|
|
51
|
+
|
|
52
|
+
The `key_value` block defines how Ruby attributes map to JSON keys:
|
|
53
|
+
|
|
54
|
+
[source,ruby]
|
|
55
|
+
----
|
|
56
|
+
key_value do
|
|
57
|
+
map "type", to: :type, render_default: true
|
|
58
|
+
map "attrs", to: :attrs
|
|
59
|
+
map "content", to: :content
|
|
60
|
+
end
|
|
61
|
+
----
|
|
62
|
+
|
|
63
|
+
=== Convenience Methods
|
|
64
|
+
|
|
65
|
+
Add helper methods for construction:
|
|
66
|
+
|
|
67
|
+
[source,ruby]
|
|
68
|
+
----
|
|
69
|
+
class Callout < Prosereflect::Node
|
|
70
|
+
PM_TYPE = "callout"
|
|
71
|
+
|
|
72
|
+
def self.create(variant: "info", text: nil)
|
|
73
|
+
node = new(type: PM_TYPE, attrs: { "variant" => variant })
|
|
74
|
+
node.add_text(text) if text
|
|
75
|
+
node
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
----
|
|
79
|
+
|
|
80
|
+
== Serialization Round-Trip
|
|
81
|
+
|
|
82
|
+
Ensure `to_h` and `Parser` are compatible:
|
|
83
|
+
|
|
84
|
+
[source,ruby]
|
|
85
|
+
----
|
|
86
|
+
# Serialize
|
|
87
|
+
callout = Callout.create(variant: "warning", text: "Be careful!")
|
|
88
|
+
hash = callout.to_h
|
|
89
|
+
# => {"type" => "callout", "attrs" => {"variant" => "warning"},
|
|
90
|
+
# "content" => [{"type" => "text", "text" => "Be careful!"}]}
|
|
91
|
+
|
|
92
|
+
# Deserialize (if registered with Parser)
|
|
93
|
+
doc = Prosereflect::Parser.parse_document({
|
|
94
|
+
"type" => "doc",
|
|
95
|
+
"content" => [hash]
|
|
96
|
+
})
|
|
97
|
+
----
|
|
98
|
+
|
|
99
|
+
== Node Naming Pattern
|
|
100
|
+
|
|
101
|
+
prosereflect maps PM_TYPE strings to classes by convention:
|
|
102
|
+
|
|
103
|
+
* `"paragraph"` -> `Prosereflect::Paragraph`
|
|
104
|
+
* `"heading"` -> `Prosereflect::Heading`
|
|
105
|
+
* `"table"` -> `Prosereflect::Table`
|
|
106
|
+
|
|
107
|
+
Custom types follow the same pattern -- the parser uses `PM_TYPE` to find the matching class.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Guides
|
|
4
|
+
nav_order: 6
|
|
5
|
+
has_children: true
|
|
6
|
+
---
|
|
7
|
+
= Guides
|
|
8
|
+
|
|
9
|
+
Task-oriented tutorials for common prosereflect workflows.
|
|
10
|
+
|
|
11
|
+
* x:round-trip-html[HTML Round-Trip] -- Convert between HTML and ProseMirror
|
|
12
|
+
* x:custom-nodes[Custom Nodes] -- Define custom node types
|
|
13
|
+
* x:serialization[Serialization] -- JSON/YAML round-trips
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: HTML Round-Trip
|
|
4
|
+
parent: Guides
|
|
5
|
+
nav_order: 1
|
|
6
|
+
---
|
|
7
|
+
= HTML Round-Trip
|
|
8
|
+
|
|
9
|
+
This guide shows how to parse HTML into a ProseMirror document model, modify it, and convert back to HTML.
|
|
10
|
+
|
|
11
|
+
== Parse HTML
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
require 'prosereflect'
|
|
16
|
+
|
|
17
|
+
html = '<p>This is <strong>bold</strong> and <em>italic</em> text.</p>'
|
|
18
|
+
doc = Prosereflect::Input::Html.parse(html)
|
|
19
|
+
----
|
|
20
|
+
|
|
21
|
+
== Navigate and Modify
|
|
22
|
+
|
|
23
|
+
[source,ruby]
|
|
24
|
+
----
|
|
25
|
+
# Access the first paragraph
|
|
26
|
+
para = doc.paragraphs.first
|
|
27
|
+
para.text_content # => "This is bold and italic text."
|
|
28
|
+
|
|
29
|
+
# Append text
|
|
30
|
+
para.add_text(" More content.")
|
|
31
|
+
|
|
32
|
+
# Add a new paragraph
|
|
33
|
+
doc.add_paragraph("Second paragraph.")
|
|
34
|
+
----
|
|
35
|
+
|
|
36
|
+
== Convert Back to HTML
|
|
37
|
+
|
|
38
|
+
[source,ruby]
|
|
39
|
+
----
|
|
40
|
+
modified_html = Prosereflect::Output::Html.convert(doc)
|
|
41
|
+
----
|
|
42
|
+
|
|
43
|
+
== Working with Tables
|
|
44
|
+
|
|
45
|
+
[source,ruby]
|
|
46
|
+
----
|
|
47
|
+
html = '<table><tr><th>Header</th></tr><tr><td>Data</td></tr></table>'
|
|
48
|
+
doc = Prosereflect::Input::Html.parse(html)
|
|
49
|
+
|
|
50
|
+
tables = doc.tables
|
|
51
|
+
first_table = tables.first
|
|
52
|
+
first_table.header_row # => row with TableHeader cells
|
|
53
|
+
first_table.data_rows # => rows with TableCell cells
|
|
54
|
+
----
|
|
55
|
+
|
|
56
|
+
== Round-Trip with Marks
|
|
57
|
+
|
|
58
|
+
[source,ruby]
|
|
59
|
+
----
|
|
60
|
+
html = '<p><a href="https://example.com">click here</a></p>'
|
|
61
|
+
doc = Prosereflect::Input::Html.parse(html)
|
|
62
|
+
|
|
63
|
+
# The link mark is preserved
|
|
64
|
+
para = doc.paragraphs.first
|
|
65
|
+
|
|
66
|
+
# Convert back - links are rendered as <a> tags
|
|
67
|
+
output = Prosereflect::Output::Html.convert(doc)
|
|
68
|
+
----
|
|
69
|
+
|
|
70
|
+
== Common Patterns
|
|
71
|
+
|
|
72
|
+
=== Extract Plain Text
|
|
73
|
+
|
|
74
|
+
[source,ruby]
|
|
75
|
+
----
|
|
76
|
+
doc = Prosereflect::Input::Html.parse(html_content)
|
|
77
|
+
plain_text = doc.text_content
|
|
78
|
+
----
|
|
79
|
+
|
|
80
|
+
=== Find and Replace Text
|
|
81
|
+
|
|
82
|
+
[source,ruby]
|
|
83
|
+
----
|
|
84
|
+
doc = Prosereflect::Input::Html.parse(html_content)
|
|
85
|
+
|
|
86
|
+
doc.paragraphs.each do |para|
|
|
87
|
+
# Access text content
|
|
88
|
+
text = para.text_content
|
|
89
|
+
# Modify via transforms
|
|
90
|
+
end
|
|
91
|
+
----
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Serialization
|
|
4
|
+
parent: Guides
|
|
5
|
+
nav_order: 3
|
|
6
|
+
---
|
|
7
|
+
= Serialization
|
|
8
|
+
|
|
9
|
+
prosereflect supports round-trip serialization through ProseMirror's JSON/YAML format using the `to_h` / `Parser.parse_document` methods.
|
|
10
|
+
|
|
11
|
+
== to_h (Serialize)
|
|
12
|
+
|
|
13
|
+
Every node can be serialized to a Ruby hash matching ProseMirror's JSON format:
|
|
14
|
+
|
|
15
|
+
[source,ruby]
|
|
16
|
+
----
|
|
17
|
+
doc = Prosereflect::Document.create
|
|
18
|
+
para = doc.add_paragraph("Hello")
|
|
19
|
+
para.add_text(" bold", [Prosereflect::Mark::Bold.new])
|
|
20
|
+
|
|
21
|
+
hash = doc.to_h
|
|
22
|
+
# => {
|
|
23
|
+
# "type" => "doc",
|
|
24
|
+
# "content" => [
|
|
25
|
+
# {
|
|
26
|
+
# "type" => "paragraph",
|
|
27
|
+
# "content" => [
|
|
28
|
+
# { "type" => "text", "text" => "Hello" },
|
|
29
|
+
# { "type" => "text", "text" => " bold", "marks" => [{"type" => "bold"}] }
|
|
30
|
+
# ]
|
|
31
|
+
# }
|
|
32
|
+
# ]
|
|
33
|
+
# }
|
|
34
|
+
----
|
|
35
|
+
|
|
36
|
+
== parse_document (Deserialize)
|
|
37
|
+
|
|
38
|
+
Convert a ProseMirror hash back to node objects:
|
|
39
|
+
|
|
40
|
+
[source,ruby]
|
|
41
|
+
----
|
|
42
|
+
doc = Prosereflect::Parser.parse_document(hash)
|
|
43
|
+
doc.paragraphs.first.text_content # => "Hello bold"
|
|
44
|
+
----
|
|
45
|
+
|
|
46
|
+
Works with YAML and JSON strings too:
|
|
47
|
+
|
|
48
|
+
[source,ruby]
|
|
49
|
+
----
|
|
50
|
+
# From YAML
|
|
51
|
+
doc = Prosereflect::Parser.parse_document(YAML.safe_load(yaml_string))
|
|
52
|
+
|
|
53
|
+
# From JSON
|
|
54
|
+
doc = Prosereflect::Parser.parse_document(JSON.parse(json_string))
|
|
55
|
+
----
|
|
56
|
+
|
|
57
|
+
== YAML Round-Trip
|
|
58
|
+
|
|
59
|
+
[source,ruby]
|
|
60
|
+
----
|
|
61
|
+
# Serialize to YAML
|
|
62
|
+
yaml = doc.to_yaml
|
|
63
|
+
|
|
64
|
+
# Deserialize from YAML
|
|
65
|
+
data = YAML.safe_load(yaml)
|
|
66
|
+
doc = Prosereflect::Parser.parse_document(data)
|
|
67
|
+
|
|
68
|
+
# Verify round-trip
|
|
69
|
+
doc.to_h == original_doc.to_h # => true
|
|
70
|
+
----
|
|
71
|
+
|
|
72
|
+
== JSON Round-Trip
|
|
73
|
+
|
|
74
|
+
[source,ruby]
|
|
75
|
+
----
|
|
76
|
+
require 'json'
|
|
77
|
+
|
|
78
|
+
# Serialize to JSON
|
|
79
|
+
json = JSON.pretty_generate(doc.to_h)
|
|
80
|
+
|
|
81
|
+
# Deserialize from JSON
|
|
82
|
+
data = JSON.parse(json)
|
|
83
|
+
doc = Prosereflect::Parser.parse_document(data)
|
|
84
|
+
----
|
|
85
|
+
|
|
86
|
+
== Step Serialization
|
|
87
|
+
|
|
88
|
+
Transform steps can be serialized to JSON:
|
|
89
|
+
|
|
90
|
+
[source,ruby]
|
|
91
|
+
----
|
|
92
|
+
step = Prosereflect::Transform::ReplaceStep.new(2, 5, slice)
|
|
93
|
+
json = step.to_json
|
|
94
|
+
# => {"stepType" => "replace", "from" => 2, "to" => 5, "slice" => [...]}
|
|
95
|
+
|
|
96
|
+
# Deserialize
|
|
97
|
+
step = Prosereflect::Transform::ReplaceStep.from_json(schema, json)
|
|
98
|
+
----
|
|
99
|
+
|
|
100
|
+
== to_h Behavior
|
|
101
|
+
|
|
102
|
+
The `to_h` method includes:
|
|
103
|
+
|
|
104
|
+
* `type` -- always included
|
|
105
|
+
* `attrs` -- only if non-empty
|
|
106
|
+
* `marks` -- only if non-empty
|
|
107
|
+
* `content` -- only if non-empty
|
|
108
|
+
|
|
109
|
+
This matches ProseMirror's JSON serialization format where omitted fields use defaults.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Getting Started
|
|
4
|
+
nav_order: 2
|
|
5
|
+
---
|
|
6
|
+
= Getting Started
|
|
7
|
+
|
|
8
|
+
== What is prosereflect?
|
|
9
|
+
|
|
10
|
+
`prosereflect` is a Ruby gem for working with the document structure used by the https://prosemirror.net/[ProseMirror rich text editor].
|
|
11
|
+
|
|
12
|
+
It provides a complete Ruby object model for ProseMirror documents, including:
|
|
13
|
+
|
|
14
|
+
* Parsing and serialization (JSON, YAML, HTML)
|
|
15
|
+
* A rich node hierarchy with type-safe accessors
|
|
16
|
+
* A full Transform system for step-based document manipulation
|
|
17
|
+
* Schema definition and content validation
|
|
18
|
+
* Position resolution and mapping
|
|
19
|
+
* HTML input and output with whitespace handling
|
|
20
|
+
* A test builder DSL for constructing test documents
|
|
21
|
+
|
|
22
|
+
== Installation
|
|
23
|
+
|
|
24
|
+
Add to your Gemfile:
|
|
25
|
+
|
|
26
|
+
[source,ruby]
|
|
27
|
+
----
|
|
28
|
+
gem 'prosereflect'
|
|
29
|
+
----
|
|
30
|
+
|
|
31
|
+
Or install directly:
|
|
32
|
+
|
|
33
|
+
[source,sh]
|
|
34
|
+
----
|
|
35
|
+
gem install prosereflect
|
|
36
|
+
----
|
|
37
|
+
|
|
38
|
+
== Quick Example
|
|
39
|
+
|
|
40
|
+
[source,ruby]
|
|
41
|
+
----
|
|
42
|
+
require 'prosereflect'
|
|
43
|
+
|
|
44
|
+
# Create a document
|
|
45
|
+
doc = Prosereflect::Document.create
|
|
46
|
+
doc.add_paragraph("Hello world")
|
|
47
|
+
|
|
48
|
+
# Parse from JSON hash
|
|
49
|
+
doc = Prosereflect::Parser.parse_document({
|
|
50
|
+
"type" => "doc",
|
|
51
|
+
"content" => [
|
|
52
|
+
{ "type" => "paragraph", "content" => [{ "type" => "text", "text" => "Hello" }] }
|
|
53
|
+
]
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
# Parse from HTML
|
|
57
|
+
doc = Prosereflect::Input::Html.parse('<p>Hello <strong>world</strong></p>')
|
|
58
|
+
|
|
59
|
+
# Convert to HTML
|
|
60
|
+
html = Prosereflect::Output::Html.convert(doc)
|
|
61
|
+
----
|
|
62
|
+
|
|
63
|
+
== Next Steps
|
|
64
|
+
|
|
65
|
+
* x:features:node-types[Node Types] -- all available node types
|
|
66
|
+
* x:understanding:document-model[Document Model] -- how the document tree works
|
|
67
|
+
* x:guides:round-trip-html[HTML Round-Trip] -- convert between HTML and ProseMirror
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Document API
|
|
4
|
+
parent: Reference
|
|
5
|
+
nav_order: 2
|
|
6
|
+
---
|
|
7
|
+
= Document API
|
|
8
|
+
|
|
9
|
+
== Prosereflect::Document
|
|
10
|
+
|
|
11
|
+
Top-level document container. Inherits from Node with PM_TYPE "doc".
|
|
12
|
+
|
|
13
|
+
=== Class Methods
|
|
14
|
+
|
|
15
|
+
[cols="1,3"]
|
|
16
|
+
|===
|
|
17
|
+
| Method | Description
|
|
18
|
+
|
|
19
|
+
| `Document.create(attrs)` | Create an empty document
|
|
20
|
+
|===
|
|
21
|
+
|
|
22
|
+
=== Query Methods
|
|
23
|
+
|
|
24
|
+
[cols="1,3"]
|
|
25
|
+
|===
|
|
26
|
+
| Method | Description
|
|
27
|
+
|
|
28
|
+
| `tables` | Find all direct child Table nodes
|
|
29
|
+
| `paragraphs` | Find all direct child Paragraph nodes
|
|
30
|
+
| `text_content` | Plain text from all nodes, joined by newlines
|
|
31
|
+
|===
|
|
32
|
+
|
|
33
|
+
=== Builder Methods
|
|
34
|
+
|
|
35
|
+
[cols="1,3"]
|
|
36
|
+
|===
|
|
37
|
+
| Method | Description
|
|
38
|
+
|
|
39
|
+
| `add_paragraph(text, attrs)` | Add a paragraph, optionally with text
|
|
40
|
+
| `add_heading(level)` | Add a heading with the given level
|
|
41
|
+
| `add_table(attrs)` | Add a table
|
|
42
|
+
| `add_image(src, alt)` | Add an image
|
|
43
|
+
| `add_bullet_list(attrs)` | Add a bullet list
|
|
44
|
+
| `add_ordered_list(attrs)` | Add an ordered list
|
|
45
|
+
| `add_blockquote(attrs)` | Add a blockquote
|
|
46
|
+
| `add_horizontal_rule(attrs)` | Add a horizontal rule
|
|
47
|
+
| `add_code_block_wrapper(attrs)` | Add a code block wrapper
|
|
48
|
+
| `add_user(id)` | Add a user mention node
|
|
49
|
+
|===
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Reference
|
|
4
|
+
nav_order: 7
|
|
5
|
+
has_children: true
|
|
6
|
+
---
|
|
7
|
+
= Reference
|
|
8
|
+
|
|
9
|
+
API reference documentation organized by module.
|
|
10
|
+
|
|
11
|
+
* x:node-api[Node API] -- Node base class and Document
|
|
12
|
+
* x:document-api[Document API] -- Document convenience methods
|
|
13
|
+
* x:transform-api[Transform API] -- Transform and Step classes
|
|
14
|
+
* x:schema-api[Schema API] -- Schema, NodeType, MarkType, ContentMatch
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Node API
|
|
4
|
+
parent: Reference
|
|
5
|
+
nav_order: 1
|
|
6
|
+
---
|
|
7
|
+
= Node API
|
|
8
|
+
|
|
9
|
+
== Prosereflect::Node
|
|
10
|
+
|
|
11
|
+
Base class for all document elements.
|
|
12
|
+
|
|
13
|
+
=== Attributes
|
|
14
|
+
|
|
15
|
+
* `type` (String) -- The node type (e.g. "doc", "paragraph", "text")
|
|
16
|
+
* `attrs` (Hash) -- Node-specific attributes
|
|
17
|
+
* `marks` (Array) -- Formatting marks applied to this node
|
|
18
|
+
* `content` (Array) -- Child nodes
|
|
19
|
+
|
|
20
|
+
=== Class Methods
|
|
21
|
+
|
|
22
|
+
[cols="1,3"]
|
|
23
|
+
|===
|
|
24
|
+
| Method | Description
|
|
25
|
+
|
|
26
|
+
| `Node.create(type, attrs)` | Create a new node with the given type and attrs
|
|
27
|
+
| `Node.from_json(schema, json)` | Deserialize from JSON (requires schema)
|
|
28
|
+
|===
|
|
29
|
+
|
|
30
|
+
=== Instance Methods
|
|
31
|
+
|
|
32
|
+
[cols="1,3"]
|
|
33
|
+
|===
|
|
34
|
+
| Method | Description
|
|
35
|
+
|
|
36
|
+
| `node_size` | Size in document position space
|
|
37
|
+
| `text?` | Whether this is a text node
|
|
38
|
+
| `text_content` | Combined text of all descendant text nodes
|
|
39
|
+
| `cut(from, to)` | Return a copy restricted to position range
|
|
40
|
+
| `copy(content)` | Return a copy with different content
|
|
41
|
+
| `add_child(node)` | Append a child node
|
|
42
|
+
| `find_first(type)` | Find first descendant of type
|
|
43
|
+
| `find_all(type)` | Find all descendants of type
|
|
44
|
+
| `find_children(klass)` | Find direct children of class
|
|
45
|
+
| `resolve(pos)` | Resolve position to ResolvedPos
|
|
46
|
+
| `nodes_between(from, to, &block)` | Iterate nodes in position range
|
|
47
|
+
| `descendants(&block)` | Iterate all descendant nodes
|
|
48
|
+
| `eq?(other)` | Structural equality
|
|
49
|
+
| `to_h` | Serialize to ProseMirror hash
|
|
50
|
+
| `to_yaml` | Serialize to YAML
|
|
51
|
+
|===
|
|
52
|
+
|
|
53
|
+
== Prosereflect::Text
|
|
54
|
+
|
|
55
|
+
Text node (PM_TYPE: "text").
|
|
56
|
+
|
|
57
|
+
[cols="1,3"]
|
|
58
|
+
|===
|
|
59
|
+
| Method | Description
|
|
60
|
+
|
|
61
|
+
| `text` | The text string
|
|
62
|
+
| `node_size` | `text.length + 1`
|
|
63
|
+
| `text?` | Always `true`
|
|
64
|
+
|===
|
|
65
|
+
|
|
66
|
+
== Prosereflect::Mark::Base
|
|
67
|
+
|
|
68
|
+
Base class for all marks.
|
|
69
|
+
|
|
70
|
+
[cols="1,3"]
|
|
71
|
+
|===
|
|
72
|
+
| Method | Description
|
|
73
|
+
|
|
74
|
+
| `type` | The mark type string
|
|
75
|
+
| `attrs` | Mark attributes (e.g. href for links)
|
|
76
|
+
| `to_h` | Serialize to hash
|
|
77
|
+
|===
|
|
78
|
+
|
|
79
|
+
Mark subclasses: `Bold`, `Italic`, `Code`, `Link`, `Strike`, `Subscript`, `Superscript`, `Underline`.
|