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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/links.yml +97 -0
  4. data/.gitignore +4 -0
  5. data/.rubocop_todo.yml +61 -75
  6. data/README.adoc +2 -0
  7. data/docs/Gemfile +10 -0
  8. data/docs/INDEX.adoc +45 -0
  9. data/docs/_advanced/index.adoc +15 -0
  10. data/docs/_advanced/schema.adoc +112 -0
  11. data/docs/_advanced/step-map.adoc +66 -0
  12. data/docs/_advanced/steps.adoc +88 -0
  13. data/docs/_advanced/test-builder.adoc +61 -0
  14. data/docs/_advanced/transform.adoc +92 -0
  15. data/docs/_config.yml +174 -0
  16. data/docs/_features/html-input.adoc +69 -0
  17. data/docs/_features/html-output.adoc +45 -0
  18. data/docs/_features/index.adoc +15 -0
  19. data/docs/_features/marks.adoc +86 -0
  20. data/docs/_features/node-types.adoc +124 -0
  21. data/docs/_features/user-mentions.adoc +47 -0
  22. data/docs/_guides/custom-nodes.adoc +107 -0
  23. data/docs/_guides/index.adoc +13 -0
  24. data/docs/_guides/round-trip-html.adoc +91 -0
  25. data/docs/_guides/serialization.adoc +109 -0
  26. data/docs/_pages/index.adoc +67 -0
  27. data/docs/_reference/document-api.adoc +49 -0
  28. data/docs/_reference/index.adoc +14 -0
  29. data/docs/_reference/node-api.adoc +79 -0
  30. data/docs/_reference/schema-api.adoc +95 -0
  31. data/docs/_reference/transform-api.adoc +77 -0
  32. data/docs/_understanding/document-model.adoc +65 -0
  33. data/docs/_understanding/fragment.adoc +52 -0
  34. data/docs/_understanding/index.adoc +14 -0
  35. data/docs/_understanding/resolved-position.adoc +53 -0
  36. data/docs/_understanding/slice.adoc +54 -0
  37. data/docs/lychee.toml +63 -0
  38. data/lib/prosereflect/blockquote.rb +9 -0
  39. data/lib/prosereflect/bullet_list.rb +25 -19
  40. data/lib/prosereflect/code_block.rb +1 -5
  41. data/lib/prosereflect/fragment.rb +249 -0
  42. data/lib/prosereflect/horizontal_rule.rb +9 -0
  43. data/lib/prosereflect/image.rb +9 -0
  44. data/lib/prosereflect/input/html.rb +96 -0
  45. data/lib/prosereflect/node.rb +141 -3
  46. data/lib/prosereflect/ordered_list.rb +2 -0
  47. data/lib/prosereflect/output/html.rb +227 -0
  48. data/lib/prosereflect/parser.rb +9 -0
  49. data/lib/prosereflect/resolved_pos.rb +256 -0
  50. data/lib/prosereflect/schema/attribute.rb +57 -0
  51. data/lib/prosereflect/schema/content_match.rb +656 -0
  52. data/lib/prosereflect/schema/fragment.rb +166 -0
  53. data/lib/prosereflect/schema/mark.rb +121 -0
  54. data/lib/prosereflect/schema/mark_type.rb +130 -0
  55. data/lib/prosereflect/schema/node.rb +236 -0
  56. data/lib/prosereflect/schema/node_type.rb +274 -0
  57. data/lib/prosereflect/schema/schema_main.rb +190 -0
  58. data/lib/prosereflect/schema/spec.rb +92 -0
  59. data/lib/prosereflect/schema.rb +39 -0
  60. data/lib/prosereflect/text.rb +24 -0
  61. data/lib/prosereflect/transform/attr_step.rb +157 -0
  62. data/lib/prosereflect/transform/insert_step.rb +115 -0
  63. data/lib/prosereflect/transform/mapping.rb +82 -0
  64. data/lib/prosereflect/transform/mark_step.rb +269 -0
  65. data/lib/prosereflect/transform/replace_around_step.rb +181 -0
  66. data/lib/prosereflect/transform/replace_step.rb +157 -0
  67. data/lib/prosereflect/transform/slice.rb +91 -0
  68. data/lib/prosereflect/transform/step.rb +89 -0
  69. data/lib/prosereflect/transform/step_map.rb +126 -0
  70. data/lib/prosereflect/transform/structure.rb +120 -0
  71. data/lib/prosereflect/transform/transform.rb +341 -0
  72. data/lib/prosereflect/transform.rb +26 -0
  73. data/lib/prosereflect/version.rb +1 -1
  74. data/lib/prosereflect.rb +3 -0
  75. data/spec/fixtures/documents/formatted_text.yaml +14 -0
  76. data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
  77. data/spec/fixtures/documents/lists_doc.yaml +32 -0
  78. data/spec/fixtures/documents/mixed_content.yaml +40 -0
  79. data/spec/fixtures/documents/nested_doc.yaml +20 -0
  80. data/spec/fixtures/documents/simple_doc.yaml +6 -0
  81. data/spec/fixtures/documents/table_doc.yaml +32 -0
  82. data/spec/fixtures/documents/transform_test.yaml +14 -0
  83. data/spec/fixtures/schema/custom_schema.rb +37 -0
  84. data/spec/fixtures/schema/test_schema.rb +46 -0
  85. data/spec/fixtures/test_builder/helpers.rb +212 -0
  86. data/spec/prosereflect/document_spec.rb +1 -1
  87. data/spec/prosereflect/fragment_spec.rb +273 -0
  88. data/spec/prosereflect/input/html_spec.rb +197 -1
  89. data/spec/prosereflect/node_spec.rb +128 -0
  90. data/spec/prosereflect/output/whitespace_spec.rb +248 -0
  91. data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
  92. data/spec/prosereflect/resolved_pos_spec.rb +74 -0
  93. data/spec/prosereflect/schema/conftest.rb +68 -0
  94. data/spec/prosereflect/schema/content_match_spec.rb +237 -0
  95. data/spec/prosereflect/schema/mark_spec.rb +274 -0
  96. data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
  97. data/spec/prosereflect/schema/node_type_spec.rb +142 -0
  98. data/spec/prosereflect/schema/schema_spec.rb +194 -0
  99. data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
  100. data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
  101. data/spec/prosereflect/transform/mapping_spec.rb +226 -0
  102. data/spec/prosereflect/transform/replace_spec.rb +832 -0
  103. data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
  104. data/spec/prosereflect/transform/slice_spec.rb +48 -0
  105. data/spec/prosereflect/transform/step_map_spec.rb +70 -0
  106. data/spec/prosereflect/transform/step_spec.rb +211 -0
  107. data/spec/prosereflect/transform/structure_spec.rb +98 -0
  108. data/spec/prosereflect/transform/transform_spec.rb +238 -0
  109. data/spec/spec_helper.rb +1 -0
  110. metadata +90 -2
@@ -0,0 +1,88 @@
1
+ ---
2
+ layout: default
3
+ title: Step Types
4
+ parent: Advanced
5
+ nav_order: 2
6
+ ---
7
+ = Step Types
8
+
9
+ Each step is an atomic document change. All steps inherit from `Prosereflect::Transform::Step`.
10
+
11
+ == Step Base Class
12
+
13
+ Every step implements:
14
+
15
+ * `apply(doc)` -- apply to a document, returns `Result` (ok/fail)
16
+ * `get_map` -- returns a `StepMap` for position tracking
17
+ * `invert(doc)` -- returns a step that undoes this one
18
+ * `merge(other)` -- optionally merge with another step (returns nil if not possible)
19
+ * `to_json` -- serialize to JSON
20
+ * `step_type` -- string identifier for the step type
21
+
22
+ == ReplaceStep
23
+
24
+ Replaces a range of content with a slice:
25
+
26
+ [source,ruby]
27
+ ----
28
+ step = Prosereflect::Transform::ReplaceStep.new(from, to, slice)
29
+ result = step.apply(doc)
30
+ # step_type: "replace"
31
+ ----
32
+
33
+ == ReplaceAroundStep
34
+
35
+ Replaces content while preserving a "gap" -- used by lift and wrap operations:
36
+
37
+ [source,ruby]
38
+ ----
39
+ step = Prosereflect::Transform::ReplaceAroundStep.new(
40
+ from, to, gap_from, gap_to, slice, insert, structure: false
41
+ )
42
+ # step_type: "replaceAround"
43
+ ----
44
+
45
+ The `structure` flag prevents the step from overwriting content in the non-gap range.
46
+
47
+ == AddMarkStep / RemoveMarkStep
48
+
49
+ Add or remove a mark over a range:
50
+
51
+ [source,ruby]
52
+ ----
53
+ add_step = Prosereflect::Transform::AddMarkStep.new(from, to, mark)
54
+ remove_step = Prosereflect::Transform::RemoveMarkStep.new(from, to, mark)
55
+ # step_types: "addMark", "removeMark"
56
+ ----
57
+
58
+ == AttrStep
59
+
60
+ Set attributes on a node at a position:
61
+
62
+ [source,ruby]
63
+ ----
64
+ step = Prosereflect::Transform::AttrStep.new(pos, { "level" => 3 })
65
+ # step_type: "attr"
66
+ ----
67
+
68
+ == InsertStep / DeleteStep
69
+
70
+ Insert or delete content at a position:
71
+
72
+ [source,ruby]
73
+ ----
74
+ insert_step = Prosereflect::Transform::InsertStep.new(pos, content)
75
+ delete_step = Prosereflect::Transform::DeleteStep.new(from, to)
76
+ ----
77
+
78
+ == Result
79
+
80
+ `Step::Result` indicates success or failure:
81
+
82
+ [source,ruby]
83
+ ----
84
+ result = step.apply(doc)
85
+ result.ok? # => true if successful
86
+ result.doc # => the new document (on success)
87
+ result.failed # => error message (on failure)
88
+ ----
@@ -0,0 +1,61 @@
1
+ ---
2
+ layout: default
3
+ title: Test Builder
4
+ parent: Advanced
5
+ nav_order: 5
6
+ ---
7
+ = Test Builder
8
+
9
+ `TestBuilder` provides a DSL for constructing ProseMirror documents in tests, inspired by ProseMirror's test-builder.
10
+
11
+ == Basic Usage
12
+
13
+ [source,ruby]
14
+ ----
15
+ require 'prosereflect/test_builder'
16
+
17
+ # Parse a document from DSL notation
18
+ doc = TestBuilder.parse('doc(p("hello world"))')
19
+ ----
20
+
21
+ == DSL Syntax
22
+
23
+ The DSL uses function-call syntax to build document trees:
24
+
25
+ * `doc(...)` -- document node
26
+ * `p(...)` -- paragraph node
27
+ * `h1(...)` through `h6(...)` -- heading nodes
28
+ * `text(...)` -- text node
29
+ * `table(...)` -- table node
30
+ * `ul(...)` / `ol(...)` -- list nodes
31
+
32
+ Strings in quotes become text nodes:
33
+
34
+ [source,ruby]
35
+ ----
36
+ doc(p("hello")) # paragraph with text
37
+ doc(p("hello ", bold("world"))) # paragraph with bold text
38
+ ----
39
+
40
+ == Position Markers
41
+
42
+ Insert markers to track positions in tests:
43
+
44
+ * `<|>` -- cursor position
45
+ * `<|name>` -- named anchor
46
+ * `<1>` -- numbered position
47
+
48
+ [source,ruby]
49
+ ----
50
+ content, positions = TestBuilder.extract_markers('doc(p("hello<|a> world<|b>"))')
51
+ positions["a"] # => position of first marker
52
+ positions["b"] # => position of second marker
53
+ ----
54
+
55
+ == Schema Integration
56
+
57
+ [source,ruby]
58
+ ----
59
+ builder = TestBuilder.for_schema(schema)
60
+ doc = builder.parse('doc(p("hello"))')
61
+ ----
@@ -0,0 +1,92 @@
1
+ ---
2
+ layout: default
3
+ title: Transforms
4
+ parent: Advanced
5
+ nav_order: 1
6
+ ---
7
+ = Transforms
8
+
9
+ `Prosereflect::Transform::Transform` provides a chainable API for composing document changes as a sequence of steps. Each transform accumulates steps and their position mappings.
10
+
11
+ == Creating a Transform
12
+
13
+ [source,ruby]
14
+ ----
15
+ doc = Prosereflect::Parser.parse_document(data)
16
+ tx = Prosereflect::Transform::Transform.new(doc)
17
+ ----
18
+
19
+ == Operations
20
+
21
+ === Adding and Removing Marks
22
+
23
+ [source,ruby]
24
+ ----
25
+ mark = Prosereflect::Mark::Bold.new
26
+ tx.add_mark(1, 4, mark) # Bold text from position 1 to 4
27
+ tx.remove_mark(1, 4, mark) # Remove bold from position 1 to 4
28
+ ----
29
+
30
+ === Inserting and Deleting
31
+
32
+ [source,ruby]
33
+ ----
34
+ tx.insert(5, some_content) # Insert content at position 5
35
+ tx.delete(2, 7) # Delete content from position 2 to 7
36
+ ----
37
+
38
+ === Replacing
39
+
40
+ [source,ruby]
41
+ ----
42
+ slice = Prosereflect::Transform::Slice.new(fragment)
43
+ tx.replace(2, 7, slice) # Replace positions 2-7 with slice content
44
+ tx.replace_with(2, 7, node) # Replace positions 2-7 with nodes
45
+ ----
46
+
47
+ === Structural Operations
48
+
49
+ [source,ruby]
50
+ ----
51
+ tx.split(5) # Split the document at position 5
52
+ tx.join(5) # Join two nodes at position 5
53
+ tx.lift(range, target_depth) # Lift content out of wrapper
54
+ tx.wrap(range, wrappers) # Wrap content in new nodes
55
+ ----
56
+
57
+ === Setting Attributes
58
+
59
+ [source,ruby]
60
+ ----
61
+ tx.set_node_attribute(pos, { "level" => 3 })
62
+ tx.set_doc_attribute({ "meta" => "value" })
63
+ ----
64
+
65
+ == Applying Transforms
66
+
67
+ [source,ruby]
68
+ ----
69
+ # Apply all steps and get the resulting document
70
+ new_doc = tx.doc
71
+
72
+ # Check step count
73
+ tx.size # => number of accumulated steps
74
+ tx.empty? # => whether any steps exist
75
+ ----
76
+
77
+ == Mappings
78
+
79
+ Each step records a `StepMap` describing how positions change. The transform's `mapping` tracks all maps:
80
+
81
+ [source,ruby]
82
+ ----
83
+ tx.maps # => array of StepMap objects
84
+ tx.mapping.map(pos) # => map a position through all steps
85
+ ----
86
+
87
+ == Rollback
88
+
89
+ [source,ruby]
90
+ ----
91
+ tx.rollback # Undo the last step
92
+ ----
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 &copy; 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
+ ----