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,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 © 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
|
+
----
|