prosereflect 0.1.1 → 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/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +19 -1
- data/.rubocop_todo.yml +119 -183
- data/CLAUDE.md +78 -0
- data/Gemfile +8 -4
- data/README.adoc +2 -0
- data/Rakefile +3 -3
- 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/attribute/base.rb +4 -6
- data/lib/prosereflect/attribute/bold.rb +2 -4
- data/lib/prosereflect/attribute/href.rb +1 -3
- data/lib/prosereflect/attribute/id.rb +7 -7
- data/lib/prosereflect/attribute.rb +4 -7
- data/lib/prosereflect/blockquote.rb +19 -11
- data/lib/prosereflect/bullet_list.rb +36 -29
- data/lib/prosereflect/code_block.rb +23 -27
- data/lib/prosereflect/code_block_wrapper.rb +12 -13
- data/lib/prosereflect/document.rb +14 -22
- data/lib/prosereflect/fragment.rb +249 -0
- data/lib/prosereflect/hard_break.rb +6 -6
- data/lib/prosereflect/heading.rb +14 -15
- data/lib/prosereflect/horizontal_rule.rb +23 -14
- data/lib/prosereflect/image.rb +32 -23
- data/lib/prosereflect/input/html.rb +179 -104
- data/lib/prosereflect/input.rb +7 -0
- data/lib/prosereflect/list_item.rb +11 -12
- data/lib/prosereflect/mark/base.rb +9 -11
- data/lib/prosereflect/mark/bold.rb +1 -3
- data/lib/prosereflect/mark/code.rb +1 -3
- data/lib/prosereflect/mark/italic.rb +1 -3
- data/lib/prosereflect/mark/link.rb +1 -3
- data/lib/prosereflect/mark/strike.rb +1 -3
- data/lib/prosereflect/mark/subscript.rb +1 -3
- data/lib/prosereflect/mark/superscript.rb +1 -3
- data/lib/prosereflect/mark/underline.rb +1 -3
- data/lib/prosereflect/mark.rb +9 -5
- data/lib/prosereflect/node.rb +171 -33
- data/lib/prosereflect/ordered_list.rb +17 -14
- data/lib/prosereflect/output/html.rb +279 -50
- data/lib/prosereflect/output.rb +7 -0
- data/lib/prosereflect/paragraph.rb +11 -13
- data/lib/prosereflect/parser.rb +56 -66
- 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/table.rb +12 -13
- data/lib/prosereflect/table_cell.rb +13 -13
- data/lib/prosereflect/table_header.rb +17 -17
- data/lib/prosereflect/table_row.rb +12 -12
- data/lib/prosereflect/text.rb +35 -11
- 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/user.rb +15 -15
- data/lib/prosereflect/version.rb +1 -1
- data/lib/prosereflect.rb +30 -17
- data/prosereflect.gemspec +17 -16
- 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 +332 -330
- data/spec/prosereflect/fragment_spec.rb +273 -0
- data/spec/prosereflect/hard_break_spec.rb +125 -125
- data/spec/prosereflect/input/html_spec.rb +718 -522
- data/spec/prosereflect/node_spec.rb +311 -182
- data/spec/prosereflect/output/html_spec.rb +105 -105
- data/spec/prosereflect/output/whitespace_spec.rb +248 -0
- data/spec/prosereflect/paragraph_spec.rb +275 -274
- data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
- data/spec/prosereflect/parser_spec.rb +185 -180
- 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/table_cell_spec.rb +183 -183
- data/spec/prosereflect/table_row_spec.rb +149 -149
- data/spec/prosereflect/table_spec.rb +320 -318
- data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
- data/spec/prosereflect/text_spec.rb +133 -132
- 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/prosereflect/user_spec.rb +31 -28
- data/spec/prosereflect_spec.rb +28 -26
- data/spec/spec_helper.rb +7 -6
- data/spec/support/matchers.rb +6 -6
- data/spec/support/shared_examples.rb +49 -49
- metadata +96 -5
- data/spec/prosereflect/version_spec.rb +0 -11
data/docs/_config.yml
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Jekyll configuration for Prosereflect documentation
|
|
2
|
+
# Uses Just the Docs theme - https://just-the-docs.com/
|
|
3
|
+
|
|
4
|
+
# Site settings
|
|
5
|
+
title: Prosereflect Documentation
|
|
6
|
+
description: Ruby library for parsing, manipulating, and traversing ProseMirror rich text editor document trees
|
|
7
|
+
url: https://metanorma.org
|
|
8
|
+
baseurl: /prosereflect
|
|
9
|
+
|
|
10
|
+
# Repository
|
|
11
|
+
repository: metanorma/prosereflect
|
|
12
|
+
|
|
13
|
+
# Theme
|
|
14
|
+
theme: just-the-docs
|
|
15
|
+
remote_theme: just-the-docs/just-the-docs@v0.7.0
|
|
16
|
+
color_scheme: light
|
|
17
|
+
|
|
18
|
+
# AsciiDoc support
|
|
19
|
+
asciidoc: {}
|
|
20
|
+
asciidoctor:
|
|
21
|
+
attributes:
|
|
22
|
+
idprefix: ''
|
|
23
|
+
idseparator: '-'
|
|
24
|
+
source-highlighter: rouge
|
|
25
|
+
icons: font
|
|
26
|
+
experimental: true
|
|
27
|
+
sectanchors: true
|
|
28
|
+
linkattrs: true
|
|
29
|
+
sectnums: true
|
|
30
|
+
toc: left
|
|
31
|
+
toclevels: 3
|
|
32
|
+
|
|
33
|
+
# Search configuration
|
|
34
|
+
search_enabled: true
|
|
35
|
+
search:
|
|
36
|
+
heading_level: 3
|
|
37
|
+
previews: 3
|
|
38
|
+
preview_words_before: 5
|
|
39
|
+
preview_words_after: 10
|
|
40
|
+
tokenizer_separator: /[\s/]+/
|
|
41
|
+
rel_url: true
|
|
42
|
+
button: true
|
|
43
|
+
|
|
44
|
+
# Navigation
|
|
45
|
+
nav_sort: case_insensitive
|
|
46
|
+
nav_fold: true
|
|
47
|
+
|
|
48
|
+
# External links
|
|
49
|
+
aux_links:
|
|
50
|
+
"Source":
|
|
51
|
+
- "https://github.com/metanorma/prosereflect"
|
|
52
|
+
"Report Issues":
|
|
53
|
+
- "https://github.com/metanorma/prosereflect/issues"
|
|
54
|
+
|
|
55
|
+
aux_links_new_tab: true
|
|
56
|
+
|
|
57
|
+
# Back to top
|
|
58
|
+
back_to_top: true
|
|
59
|
+
back_to_top_text: "Back to top"
|
|
60
|
+
|
|
61
|
+
# Heading anchors
|
|
62
|
+
heading_anchors: true
|
|
63
|
+
|
|
64
|
+
# Footer
|
|
65
|
+
footer_content: 'Copyright © 2025 Ribose. Distributed under the <a href="https://github.com/metanorma/prosereflect/blob/main/LICENSE">BSD 2-Clause License</a>.'
|
|
66
|
+
|
|
67
|
+
# Footer last edit timestamp
|
|
68
|
+
last_edit_timestamp: true
|
|
69
|
+
last_edit_time_format: "%b %e %Y at %I:%M %p"
|
|
70
|
+
|
|
71
|
+
# Enable code copy button
|
|
72
|
+
enable_copy_code_button: true
|
|
73
|
+
|
|
74
|
+
# Callouts
|
|
75
|
+
callouts_level: quiet
|
|
76
|
+
callouts:
|
|
77
|
+
highlight:
|
|
78
|
+
color: yellow
|
|
79
|
+
important:
|
|
80
|
+
title: Important
|
|
81
|
+
color: blue
|
|
82
|
+
new:
|
|
83
|
+
title: New
|
|
84
|
+
color: green
|
|
85
|
+
note:
|
|
86
|
+
title: Note
|
|
87
|
+
color: purple
|
|
88
|
+
warning:
|
|
89
|
+
title: Warning
|
|
90
|
+
color: red
|
|
91
|
+
|
|
92
|
+
# Plugins
|
|
93
|
+
plugins:
|
|
94
|
+
- jekyll-asciidoc
|
|
95
|
+
- jekyll-seo-tag
|
|
96
|
+
- jekyll-sitemap
|
|
97
|
+
|
|
98
|
+
# Markdown settings (for any markdown files)
|
|
99
|
+
markdown: kramdown
|
|
100
|
+
kramdown:
|
|
101
|
+
input: GFM
|
|
102
|
+
hard_wrap: false
|
|
103
|
+
syntax_highlighter: rouge
|
|
104
|
+
|
|
105
|
+
# Collections for organizing content
|
|
106
|
+
collections:
|
|
107
|
+
pages:
|
|
108
|
+
permalink: "/:path/"
|
|
109
|
+
output: true
|
|
110
|
+
features:
|
|
111
|
+
permalink: "/:collection/:path/"
|
|
112
|
+
output: true
|
|
113
|
+
understanding:
|
|
114
|
+
permalink: "/:collection/:path/"
|
|
115
|
+
output: true
|
|
116
|
+
advanced:
|
|
117
|
+
permalink: "/:collection/:path/"
|
|
118
|
+
output: true
|
|
119
|
+
guides:
|
|
120
|
+
permalink: "/:collection/:path/"
|
|
121
|
+
output: true
|
|
122
|
+
reference:
|
|
123
|
+
permalink: "/:collection/:path/"
|
|
124
|
+
output: true
|
|
125
|
+
|
|
126
|
+
# Just the Docs collection configuration
|
|
127
|
+
just_the_docs:
|
|
128
|
+
collections:
|
|
129
|
+
pages:
|
|
130
|
+
name: Pages
|
|
131
|
+
nav_fold: false
|
|
132
|
+
features:
|
|
133
|
+
name: Features
|
|
134
|
+
nav_fold: true
|
|
135
|
+
understanding:
|
|
136
|
+
name: Understanding
|
|
137
|
+
nav_fold: true
|
|
138
|
+
advanced:
|
|
139
|
+
name: Advanced
|
|
140
|
+
nav_fold: true
|
|
141
|
+
guides:
|
|
142
|
+
name: Guides
|
|
143
|
+
nav_fold: true
|
|
144
|
+
reference:
|
|
145
|
+
name: Reference
|
|
146
|
+
nav_fold: true
|
|
147
|
+
|
|
148
|
+
# Defaults
|
|
149
|
+
defaults:
|
|
150
|
+
- scope:
|
|
151
|
+
path: ""
|
|
152
|
+
type: pages
|
|
153
|
+
values:
|
|
154
|
+
layout: default
|
|
155
|
+
|
|
156
|
+
# Include additional files
|
|
157
|
+
include:
|
|
158
|
+
- "*.adoc"
|
|
159
|
+
|
|
160
|
+
# Exclude from processing
|
|
161
|
+
exclude:
|
|
162
|
+
- Gemfile
|
|
163
|
+
- Gemfile.lock
|
|
164
|
+
- node_modules
|
|
165
|
+
- vendor
|
|
166
|
+
- .sass-cache
|
|
167
|
+
- .jekyll-cache
|
|
168
|
+
- .bundle
|
|
169
|
+
- README.md
|
|
170
|
+
- LICENSE
|
|
171
|
+
- .git
|
|
172
|
+
- .gitignore
|
|
173
|
+
|
|
174
|
+
permalink: pretty
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: HTML Input
|
|
4
|
+
parent: Features
|
|
5
|
+
nav_order: 3
|
|
6
|
+
---
|
|
7
|
+
= HTML Input
|
|
8
|
+
|
|
9
|
+
`Prosereflect::Input::Html` parses HTML strings into ProseMirror document models using Nokogiri.
|
|
10
|
+
|
|
11
|
+
== Basic Parsing
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
require 'prosereflect'
|
|
16
|
+
|
|
17
|
+
html = '<p>Hello <strong>bold</strong> and <em>italic</em></p>'
|
|
18
|
+
doc = Prosereflect::Input::Html.parse(html)
|
|
19
|
+
|
|
20
|
+
para = doc.paragraphs.first
|
|
21
|
+
para.text_content # => "Hello bold and italic"
|
|
22
|
+
----
|
|
23
|
+
|
|
24
|
+
== Supported Elements
|
|
25
|
+
|
|
26
|
+
The HTML parser maps standard HTML elements to ProseMirror node types:
|
|
27
|
+
|
|
28
|
+
[cols="2,2"]
|
|
29
|
+
|===
|
|
30
|
+
| HTML | ProseMirror Type
|
|
31
|
+
|
|
32
|
+
| `<p>` | paragraph
|
|
33
|
+
| `<h1>` - `<h6>` | heading (with level attr)
|
|
34
|
+
| `<ul>` / `<ol>` | bullet_list / ordered_list
|
|
35
|
+
| `<li>` | list_item
|
|
36
|
+
| `<table>`, `<tr>`, `<td>`, `<th>` | table, table_row, table_cell, table_header
|
|
37
|
+
| `<blockquote>` | blockquote
|
|
38
|
+
| `<pre><code>` | code_block_wrapper / code_block
|
|
39
|
+
| `<img>` | image
|
|
40
|
+
| `<hr>` | horizontal_rule
|
|
41
|
+
| `<br>` | hard_break
|
|
42
|
+
| `<user-mention>` | user
|
|
43
|
+
|===
|
|
44
|
+
|
|
45
|
+
== Supported Inline Formatting
|
|
46
|
+
|
|
47
|
+
[cols="2,2"]
|
|
48
|
+
|===
|
|
49
|
+
| HTML | Mark Type
|
|
50
|
+
|
|
51
|
+
| `<strong>`, `<b>` | bold
|
|
52
|
+
| `<em>`, `<i>` | italic
|
|
53
|
+
| `<code>` | code
|
|
54
|
+
| `<a>` | link (with href)
|
|
55
|
+
| `<s>`, `<del>` | strike
|
|
56
|
+
| `<sub>` | subscript
|
|
57
|
+
| `<sup>` | superscript
|
|
58
|
+
| `<u>` | underline
|
|
59
|
+
|===
|
|
60
|
+
|
|
61
|
+
== User Mentions
|
|
62
|
+
|
|
63
|
+
[source,ruby]
|
|
64
|
+
----
|
|
65
|
+
html = '<p>Hello <user-mention data-id="123"></user-mention>!</p>'
|
|
66
|
+
doc = Prosereflect::Input::Html.parse(html)
|
|
67
|
+
users = doc.find_all('user')
|
|
68
|
+
users.first.id # => "123"
|
|
69
|
+
----
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: HTML Output
|
|
4
|
+
parent: Features
|
|
5
|
+
nav_order: 4
|
|
6
|
+
---
|
|
7
|
+
= HTML Output
|
|
8
|
+
|
|
9
|
+
`Prosereflect::Output::Html` converts ProseMirror document models to HTML strings using Nokogiri.
|
|
10
|
+
|
|
11
|
+
== Basic Conversion
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
require 'prosereflect'
|
|
16
|
+
|
|
17
|
+
doc = Prosereflect::Document.create
|
|
18
|
+
para = doc.add_paragraph("Hello")
|
|
19
|
+
para.add_text(" bold", [Prosereflect::Mark::Bold.new])
|
|
20
|
+
|
|
21
|
+
html = Prosereflect::Output::Html.convert(doc)
|
|
22
|
+
# => "<html><body><p>Hello<strong> bold</strong></p></body></html>"
|
|
23
|
+
----
|
|
24
|
+
|
|
25
|
+
== DOMSerializer
|
|
26
|
+
|
|
27
|
+
`Prosereflect::Output::DOMSerializer` provides fine-grained control over HTML output:
|
|
28
|
+
|
|
29
|
+
* `serialize_node(node)` -- serialize a single node
|
|
30
|
+
* `render_text(text, marks)` -- render text with marks applied
|
|
31
|
+
|
|
32
|
+
== Whitespace Handling
|
|
33
|
+
|
|
34
|
+
The serializer handles whitespace differently depending on the parent node type:
|
|
35
|
+
|
|
36
|
+
* **Code blocks** (`code_block`, `code_block_wrapper`): whitespace is preserved exactly
|
|
37
|
+
* **Regular blocks** (`paragraph`, `heading`): whitespace is collapsed (multiple spaces become one)
|
|
38
|
+
* **`white-space: pre`** nodes: whitespace is preserved
|
|
39
|
+
|
|
40
|
+
Methods involved:
|
|
41
|
+
|
|
42
|
+
* `whitespace_mode(node)` -- returns `:preserve` or `:collapse`
|
|
43
|
+
* `collapse_whitespace(text)` -- collapses runs of whitespace
|
|
44
|
+
* `normalize_whitespace(text)` -- replaces tabs/newlines with spaces
|
|
45
|
+
* `process_text_whitespace(text, node)` -- applies the appropriate mode
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Features
|
|
4
|
+
nav_order: 3
|
|
5
|
+
has_children: true
|
|
6
|
+
---
|
|
7
|
+
= Features
|
|
8
|
+
|
|
9
|
+
prosereflect provides a comprehensive set of features for working with ProseMirror documents in Ruby.
|
|
10
|
+
|
|
11
|
+
* x:node-types[Node Types] -- All available node types and their attributes
|
|
12
|
+
* x:marks[Marks] -- Text formatting marks (bold, italic, links, etc.)
|
|
13
|
+
* x:html-input[HTML Input] -- Parse HTML into ProseMirror documents
|
|
14
|
+
* x:html-output[HTML Output] -- Serialize documents to HTML
|
|
15
|
+
* x:user-mentions[User Mentions] -- @mention support in documents
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Marks
|
|
4
|
+
parent: Features
|
|
5
|
+
nav_order: 2
|
|
6
|
+
---
|
|
7
|
+
= Marks
|
|
8
|
+
|
|
9
|
+
Marks represent inline formatting applied to text nodes. prosereflect supports all standard ProseMirror marks.
|
|
10
|
+
|
|
11
|
+
== Creating Marks
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
bold = Prosereflect::Mark::Bold.new
|
|
16
|
+
italic = Prosereflect::Mark::Italic.new
|
|
17
|
+
link = Prosereflect::Mark::Link.new(href: "https://example.com")
|
|
18
|
+
----
|
|
19
|
+
|
|
20
|
+
== Applying Marks to Text
|
|
21
|
+
|
|
22
|
+
When adding text to a node, pass marks as the second argument:
|
|
23
|
+
|
|
24
|
+
[source,ruby]
|
|
25
|
+
----
|
|
26
|
+
doc = Prosereflect::Document.create
|
|
27
|
+
para = doc.add_paragraph("Plain text")
|
|
28
|
+
para.add_text(" bold", [Prosereflect::Mark::Bold.new])
|
|
29
|
+
para.add_text(" italic", [Prosereflect::Mark::Italic.new])
|
|
30
|
+
para.add_text(" both", [Prosereflect::Mark::Bold.new, Prosereflect::Mark::Italic.new])
|
|
31
|
+
----
|
|
32
|
+
|
|
33
|
+
== Available Mark Types
|
|
34
|
+
|
|
35
|
+
[cols="1,1,3"]
|
|
36
|
+
|===
|
|
37
|
+
| Class | PM Type | Attributes
|
|
38
|
+
|
|
39
|
+
| `Mark::Bold` | `bold` | none
|
|
40
|
+
| `Mark::Italic` | `italic` | none
|
|
41
|
+
| `Mark::Code` | `code` | none
|
|
42
|
+
| `Mark::Link` | `link` | `href`
|
|
43
|
+
| `Mark::Strike` | `strike` | none
|
|
44
|
+
| `Mark::Subscript` | `subscript` | none
|
|
45
|
+
| `Mark::Superscript` | `superscript` | none
|
|
46
|
+
| `Mark::Underline` | `underline` | none
|
|
47
|
+
|===
|
|
48
|
+
|
|
49
|
+
== Link Marks
|
|
50
|
+
|
|
51
|
+
Link marks carry an `href` attribute:
|
|
52
|
+
|
|
53
|
+
[source,ruby]
|
|
54
|
+
----
|
|
55
|
+
link_mark = Prosereflect::Mark::Link.new
|
|
56
|
+
link_mark.href = "https://example.com"
|
|
57
|
+
# or via constructor:
|
|
58
|
+
link_mark = Prosereflect::Mark::Link.new(href: "https://example.com")
|
|
59
|
+
----
|
|
60
|
+
|
|
61
|
+
== Serialization
|
|
62
|
+
|
|
63
|
+
Marks are serialized in the ProseMirror JSON format:
|
|
64
|
+
|
|
65
|
+
[source,ruby]
|
|
66
|
+
----
|
|
67
|
+
bold = Prosereflect::Mark::Bold.new
|
|
68
|
+
bold.to_h # => {"type" => "bold"}
|
|
69
|
+
|
|
70
|
+
link = Prosereflect::Mark::Link.new(href: "https://example.com")
|
|
71
|
+
link.to_h # => {"type" => "link", "attrs" => {"href" => "https://example.com"}}
|
|
72
|
+
----
|
|
73
|
+
|
|
74
|
+
== Schema-based Marks
|
|
75
|
+
|
|
76
|
+
When using a Schema, marks can be created with validation:
|
|
77
|
+
|
|
78
|
+
[source,ruby]
|
|
79
|
+
----
|
|
80
|
+
schema = Prosereflect::Schema.new(
|
|
81
|
+
nodes_spec: { "doc" => { content: "block+" }, "paragraph" => { content: "inline*", group: "block" }, "text" => { group: "inline" } },
|
|
82
|
+
marks_spec: { "bold" => {}, "italic" => {} }
|
|
83
|
+
)
|
|
84
|
+
bold = schema.mark("bold")
|
|
85
|
+
italic = schema.mark("italic")
|
|
86
|
+
----
|
|
@@ -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
|