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
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'node'
|
|
4
|
-
require_relative 'table'
|
|
5
|
-
require_relative 'paragraph'
|
|
6
|
-
require_relative 'image'
|
|
7
|
-
require_relative 'bullet_list'
|
|
8
|
-
require_relative 'ordered_list'
|
|
9
|
-
require_relative 'blockquote'
|
|
10
|
-
require_relative 'horizontal_rule'
|
|
11
|
-
require_relative 'code_block_wrapper'
|
|
12
|
-
require_relative 'heading'
|
|
13
|
-
require_relative 'user'
|
|
14
|
-
|
|
15
3
|
module Prosereflect
|
|
16
4
|
# Document class represents a ProseMirror document.
|
|
17
5
|
class Document < Node
|
|
18
|
-
attribute :type, :string, default: -> {
|
|
19
|
-
|
|
6
|
+
attribute :type, :string, default: -> {
|
|
7
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
8
|
+
}
|
|
9
|
+
PM_TYPE = "doc"
|
|
20
10
|
|
|
21
11
|
key_value do
|
|
22
|
-
map
|
|
23
|
-
map
|
|
24
|
-
map
|
|
12
|
+
map "type", to: :type, render_default: true
|
|
13
|
+
map "content", to: :content
|
|
14
|
+
map "attrs", to: :attrs
|
|
25
15
|
end
|
|
26
16
|
|
|
27
17
|
def self.create(attrs = nil)
|
|
@@ -33,12 +23,12 @@ module Prosereflect
|
|
|
33
23
|
result = super
|
|
34
24
|
|
|
35
25
|
# Handle array of attribute objects specially for serialization
|
|
36
|
-
if attrs.is_a?(Array) && attrs.all?
|
|
26
|
+
if attrs.is_a?(Array) && attrs.all?(Prosereflect::Attribute::Base)
|
|
37
27
|
attrs_hash = {}
|
|
38
28
|
attrs.each do |attr|
|
|
39
29
|
attrs_hash.merge!(attr.to_h)
|
|
40
30
|
end
|
|
41
|
-
result[
|
|
31
|
+
result["attrs"] = attrs_hash unless attrs_hash.empty?
|
|
42
32
|
end
|
|
43
33
|
|
|
44
34
|
result
|
|
@@ -54,7 +44,7 @@ module Prosereflect
|
|
|
54
44
|
|
|
55
45
|
# Add a heading to the document
|
|
56
46
|
def add_heading(level)
|
|
57
|
-
heading = Heading.new(attrs: {
|
|
47
|
+
heading = Heading.new(attrs: { "level" => level })
|
|
58
48
|
add_child(heading)
|
|
59
49
|
heading
|
|
60
50
|
end
|
|
@@ -130,9 +120,11 @@ module Prosereflect
|
|
|
130
120
|
|
|
131
121
|
# Get plain text content from all nodes
|
|
132
122
|
def text_content
|
|
133
|
-
return
|
|
123
|
+
return "" unless content
|
|
134
124
|
|
|
135
|
-
content.map
|
|
125
|
+
content.map do |node|
|
|
126
|
+
node.respond_to?(:text_content) ? node.text_content : ""
|
|
127
|
+
end.join("\n").strip
|
|
136
128
|
end
|
|
137
129
|
end
|
|
138
130
|
end
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prosereflect
|
|
4
|
+
# Fragment represents a sequence of nodes.
|
|
5
|
+
# Used for document content, slice content, etc.
|
|
6
|
+
class Fragment
|
|
7
|
+
attr_reader :content
|
|
8
|
+
|
|
9
|
+
def initialize(content = [])
|
|
10
|
+
@content = if content.is_a?(Array)
|
|
11
|
+
content
|
|
12
|
+
elsif content.respond_to?(:to_a)
|
|
13
|
+
content.to_a
|
|
14
|
+
else
|
|
15
|
+
[content]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Total size of all nodes in this fragment
|
|
20
|
+
def size
|
|
21
|
+
@content.sum { |n| n.respond_to?(:node_size) ? n.node_size : n.text_content.length + 1 }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Check if fragment is empty
|
|
25
|
+
def empty?
|
|
26
|
+
@content.empty?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Append another fragment to this one
|
|
30
|
+
def append(other)
|
|
31
|
+
if other.is_a?(Fragment)
|
|
32
|
+
Fragment.new(@content + other.content)
|
|
33
|
+
else
|
|
34
|
+
Fragment.new(@content + [other])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Cut this fragment to a range
|
|
39
|
+
def cut(from = 0, to = nil)
|
|
40
|
+
to ||= size
|
|
41
|
+
|
|
42
|
+
return Fragment.new([]) if from >= to
|
|
43
|
+
|
|
44
|
+
cut_nodes(from, to)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def cut_nodes(from, to)
|
|
48
|
+
result = []
|
|
49
|
+
pos = 0
|
|
50
|
+
|
|
51
|
+
@content.each do |node|
|
|
52
|
+
node_end = pos + node.node_size
|
|
53
|
+
|
|
54
|
+
result << node if in_range_before_from?(pos, node_end, from)
|
|
55
|
+
result << node if overlaps_range?(pos, node_end, from, to)
|
|
56
|
+
|
|
57
|
+
pos = node_end
|
|
58
|
+
break if pos >= to
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Fragment.new(result)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def in_range_before_from?(_pos, node_end, from)
|
|
65
|
+
node_end <= from
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def overlaps_range?(pos, node_end, from, to)
|
|
69
|
+
(pos >= from && node_end <= to) || (pos < from && node_end > from)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Replace child at index
|
|
73
|
+
def replace_child(index, replacement)
|
|
74
|
+
new_content = @content.dup
|
|
75
|
+
new_content[index] = replacement
|
|
76
|
+
Fragment.new(new_content)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Iterate over all nodes between positions
|
|
80
|
+
def nodes_between(from, to, callback = nil, node_start = 0, &blk)
|
|
81
|
+
cb = callback || blk
|
|
82
|
+
return unless cb && to > from
|
|
83
|
+
|
|
84
|
+
pos = 0
|
|
85
|
+
|
|
86
|
+
@content.each do |node|
|
|
87
|
+
node_end = pos + node.node_size
|
|
88
|
+
next unless node_end > from
|
|
89
|
+
|
|
90
|
+
dispatch_node_callback(node, pos, node_end, from, to, cb, node_start)
|
|
91
|
+
pos = node_end
|
|
92
|
+
break if pos >= to
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def dispatch_node_callback(node, pos, node_end, from, to, callback, node_start)
|
|
97
|
+
if node.text?
|
|
98
|
+
text_node_callback(node, pos, from, node_start, callback)
|
|
99
|
+
elsif node_fully_in_range?(pos, node_end, from, to)
|
|
100
|
+
full_node_callback(node, pos, node_end, from, to, callback, node_start)
|
|
101
|
+
elsif node_overlaps_from?(pos, node_end, from)
|
|
102
|
+
partial_node_callback(node, pos, node_end, from, to, callback, node_start)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def text_node_callback(node, pos, from, node_start, callback)
|
|
107
|
+
callback.call(node, node_start + (from - pos).clamp(0, node.node_size - 1))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def node_fully_in_range?(pos, node_end, from, to)
|
|
111
|
+
pos >= from && node_end <= to
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def full_node_callback(node, _pos, _node_end, _from, _to, callback, node_start)
|
|
115
|
+
callback.call(node, node_start)
|
|
116
|
+
recurse_into_node(node, 0, node.content.size, callback, node_start)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def partial_node_callback(node, pos, _node_end, from, to, callback, node_start)
|
|
120
|
+
recurse_into_node(node, from - pos, [to - pos, node.content.size].min, callback, node_start)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def node_overlaps_from?(pos, node_end, from)
|
|
124
|
+
pos < from && node_end > from
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def recurse_into_node(node, start_pos, end_pos, callback, node_start)
|
|
128
|
+
return unless node.respond_to?(:nodes_between)
|
|
129
|
+
|
|
130
|
+
node.nodes_between(start_pos, end_pos, callback, node_start)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Iterate over all descendant nodes
|
|
134
|
+
def descendants(block, node_start = 0)
|
|
135
|
+
nodes_between(0, size, block, node_start)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Extract text content between positions
|
|
139
|
+
def text_between(_from, _to, separator = "", _block_separator = "\n")
|
|
140
|
+
result = []
|
|
141
|
+
@content.each do |node|
|
|
142
|
+
if node.respond_to?(:text)
|
|
143
|
+
result << node.text
|
|
144
|
+
elsif node.respond_to?(:text_content)
|
|
145
|
+
result << node.text_content
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
result.join(separator)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Find first position where two fragments differ
|
|
152
|
+
def find_diff_start(other)
|
|
153
|
+
min_length = [@content.length, other.content.length].min
|
|
154
|
+
|
|
155
|
+
pos = 0
|
|
156
|
+
min_length.times do |i|
|
|
157
|
+
return pos if @content[i] != other.content[i]
|
|
158
|
+
|
|
159
|
+
pos += @content[i].node_size
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
return nil if @content.length == other.content.length
|
|
163
|
+
|
|
164
|
+
pos
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Find last position where two fragments differ
|
|
168
|
+
def find_diff_end(other)
|
|
169
|
+
my_nodes = @content.reverse
|
|
170
|
+
other_nodes = other.content.reverse
|
|
171
|
+
|
|
172
|
+
i = 0
|
|
173
|
+
end_pos = size
|
|
174
|
+
|
|
175
|
+
while i < my_nodes.length && i < other_nodes.length
|
|
176
|
+
my_node = my_nodes[i]
|
|
177
|
+
other_node = other_nodes[i]
|
|
178
|
+
|
|
179
|
+
unless my_node == other_node
|
|
180
|
+
return end_pos
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
end_pos -= my_node.node_size
|
|
184
|
+
i += 1
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
nil
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Check equality
|
|
191
|
+
def eq?(other)
|
|
192
|
+
return false unless other.is_a?(Fragment)
|
|
193
|
+
|
|
194
|
+
@content.length == other.content.length &&
|
|
195
|
+
@content.zip(other.content).all? { |a, b| a.to_h == b.to_h }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
alias == eq?
|
|
199
|
+
|
|
200
|
+
# Hash for use in sets/hashes
|
|
201
|
+
def hash
|
|
202
|
+
@content.map(&:to_h).hash
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Access by index
|
|
206
|
+
def [](index)
|
|
207
|
+
@content[index]
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Iterate
|
|
211
|
+
def each(&block)
|
|
212
|
+
@content.each(&block)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Number of items
|
|
216
|
+
def length
|
|
217
|
+
@content.length
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
alias count length
|
|
221
|
+
|
|
222
|
+
# Convert to array
|
|
223
|
+
def to_a
|
|
224
|
+
@content.dup
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Create empty fragment
|
|
228
|
+
def self.empty
|
|
229
|
+
@empty ||= new([])
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Create from content
|
|
233
|
+
def self.from(content)
|
|
234
|
+
case content
|
|
235
|
+
when Fragment then content
|
|
236
|
+
when Array then new(content.flatten)
|
|
237
|
+
else new([content])
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def to_s
|
|
242
|
+
"<Fragment #{@content.length} nodes>"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def inspect
|
|
246
|
+
to_s
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'node'
|
|
4
|
-
|
|
5
3
|
module Prosereflect
|
|
6
4
|
class HardBreak < Node
|
|
7
|
-
PM_TYPE =
|
|
5
|
+
PM_TYPE = "hard_break"
|
|
8
6
|
|
|
9
|
-
attribute :type, :string, default: -> {
|
|
7
|
+
attribute :type, :string, default: -> {
|
|
8
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
9
|
+
}
|
|
10
10
|
|
|
11
11
|
key_value do
|
|
12
|
-
map
|
|
13
|
-
map
|
|
12
|
+
map "type", to: :type, render_default: true
|
|
13
|
+
map "marks", to: :marks
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.create(marks = nil)
|
data/lib/prosereflect/heading.rb
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'node'
|
|
4
|
-
require_relative 'text'
|
|
5
|
-
|
|
6
3
|
module Prosereflect
|
|
7
4
|
class Heading < Node
|
|
8
|
-
PM_TYPE =
|
|
5
|
+
PM_TYPE = "heading"
|
|
9
6
|
|
|
10
|
-
attribute :type, :string, default: -> {
|
|
7
|
+
attribute :type, :string, default: -> {
|
|
8
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
9
|
+
}
|
|
11
10
|
attribute :level, :integer
|
|
12
11
|
attribute :attrs, :hash
|
|
13
12
|
|
|
14
13
|
key_value do
|
|
15
|
-
map
|
|
16
|
-
map
|
|
17
|
-
map
|
|
18
|
-
map
|
|
14
|
+
map "type", to: :type, render_default: true
|
|
15
|
+
map "content", to: :content
|
|
16
|
+
map "attrs", to: :attrs
|
|
17
|
+
map "marks", to: :marks
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def initialize(params = {})
|
|
@@ -25,7 +24,7 @@ module Prosereflect
|
|
|
25
24
|
# Extract level from attrs if provided
|
|
26
25
|
return unless params[:attrs]
|
|
27
26
|
|
|
28
|
-
@level = params[:attrs][
|
|
27
|
+
@level = params[:attrs]["level"]
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
def self.create(attrs = nil)
|
|
@@ -35,15 +34,15 @@ module Prosereflect
|
|
|
35
34
|
def level=(value)
|
|
36
35
|
@level = value
|
|
37
36
|
self.attrs ||= {}
|
|
38
|
-
attrs[
|
|
37
|
+
attrs["level"] = value
|
|
39
38
|
end
|
|
40
39
|
|
|
41
40
|
def level
|
|
42
|
-
@level || attrs&.[](
|
|
41
|
+
@level || attrs&.[]("level")
|
|
43
42
|
end
|
|
44
43
|
|
|
45
44
|
def text_content
|
|
46
|
-
return
|
|
45
|
+
return "" unless content
|
|
47
46
|
|
|
48
47
|
content.map(&:text_content).join
|
|
49
48
|
end
|
|
@@ -56,8 +55,8 @@ module Prosereflect
|
|
|
56
55
|
|
|
57
56
|
def to_h
|
|
58
57
|
result = super
|
|
59
|
-
result[
|
|
60
|
-
result[
|
|
58
|
+
result["attrs"] ||= {}
|
|
59
|
+
result["attrs"]["level"] = level if level
|
|
61
60
|
result
|
|
62
61
|
end
|
|
63
62
|
end
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'node'
|
|
4
|
-
|
|
5
3
|
module Prosereflect
|
|
6
4
|
# HorizontalRule class represents a horizontal rule in ProseMirror.
|
|
7
5
|
class HorizontalRule < Node
|
|
8
|
-
PM_TYPE =
|
|
6
|
+
PM_TYPE = "horizontal_rule"
|
|
9
7
|
|
|
10
|
-
attribute :type, :string, default: -> {
|
|
8
|
+
attribute :type, :string, default: -> {
|
|
9
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
10
|
+
}
|
|
11
11
|
attribute :style, :string
|
|
12
12
|
attribute :width, :string
|
|
13
13
|
attribute :thickness, :integer
|
|
14
14
|
attribute :attrs, :hash
|
|
15
15
|
|
|
16
16
|
key_value do
|
|
17
|
-
map
|
|
18
|
-
map
|
|
19
|
-
map
|
|
17
|
+
map "type", to: :type, render_default: true
|
|
18
|
+
map "attrs", to: :attrs
|
|
19
|
+
map "content", to: :content
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def initialize(attributes = {})
|
|
@@ -31,36 +31,45 @@ module Prosereflect
|
|
|
31
31
|
def style=(value)
|
|
32
32
|
@style = value
|
|
33
33
|
self.attrs ||= {}
|
|
34
|
-
attrs[
|
|
34
|
+
attrs["style"] = value
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def style
|
|
38
|
-
@style || attrs&.[](
|
|
38
|
+
@style || attrs&.[]("style")
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def width=(value)
|
|
42
42
|
@width = value
|
|
43
43
|
self.attrs ||= {}
|
|
44
|
-
attrs[
|
|
44
|
+
attrs["width"] = value
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def width
|
|
48
|
-
@width || attrs&.[](
|
|
48
|
+
@width || attrs&.[]("width")
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def thickness=(value)
|
|
52
52
|
@thickness = value
|
|
53
53
|
self.attrs ||= {}
|
|
54
|
-
attrs[
|
|
54
|
+
attrs["thickness"] = value
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def thickness
|
|
58
|
-
@thickness || attrs&.[](
|
|
58
|
+
@thickness || attrs&.[]("thickness")
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# Override content-related methods since horizontal rules don't have content
|
|
62
|
+
def to_h
|
|
63
|
+
hash = super
|
|
64
|
+
if hash["attrs"]
|
|
65
|
+
%w[style width thickness].each { |k| hash["attrs"].delete(k) if hash["attrs"][k].nil? }
|
|
66
|
+
hash.delete("attrs") if hash["attrs"].empty?
|
|
67
|
+
end
|
|
68
|
+
hash
|
|
69
|
+
end
|
|
70
|
+
|
|
62
71
|
def add_child(*)
|
|
63
|
-
raise NotImplementedError,
|
|
72
|
+
raise NotImplementedError, "Horizontal rule nodes cannot have children"
|
|
64
73
|
end
|
|
65
74
|
|
|
66
75
|
def content
|
data/lib/prosereflect/image.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'node'
|
|
4
|
-
|
|
5
3
|
module Prosereflect
|
|
6
4
|
# Image class represents a ProseMirror image node.
|
|
7
5
|
# It handles image attributes like src, alt, title, dimensions, etc.
|
|
8
6
|
class Image < Node
|
|
9
|
-
PM_TYPE =
|
|
7
|
+
PM_TYPE = "image"
|
|
10
8
|
|
|
11
|
-
attribute :type, :string, default: -> {
|
|
9
|
+
attribute :type, :string, default: -> {
|
|
10
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
11
|
+
}
|
|
12
12
|
attribute :src, :string
|
|
13
13
|
attribute :alt, :string
|
|
14
14
|
attribute :title, :string
|
|
@@ -17,8 +17,8 @@ module Prosereflect
|
|
|
17
17
|
attribute :attrs, :hash
|
|
18
18
|
|
|
19
19
|
key_value do
|
|
20
|
-
map
|
|
21
|
-
map
|
|
20
|
+
map "type", to: :type, render_default: true
|
|
21
|
+
map "attrs", to: :attrs
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def initialize(attributes = {})
|
|
@@ -27,11 +27,11 @@ module Prosereflect
|
|
|
27
27
|
|
|
28
28
|
# Extract attributes from the attrs hash if provided
|
|
29
29
|
if attributes[:attrs]
|
|
30
|
-
@src = attributes[:attrs][
|
|
31
|
-
@alt = attributes[:attrs][
|
|
32
|
-
@title = attributes[:attrs][
|
|
33
|
-
@width = attributes[:attrs][
|
|
34
|
-
@height = attributes[:attrs][
|
|
30
|
+
@src = attributes[:attrs]["src"]
|
|
31
|
+
@alt = attributes[:attrs]["alt"]
|
|
32
|
+
@title = attributes[:attrs]["title"]
|
|
33
|
+
@width = attributes[:attrs]["width"]
|
|
34
|
+
@height = attributes[:attrs]["height"]
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
super
|
|
@@ -45,35 +45,35 @@ module Prosereflect
|
|
|
45
45
|
def src=(src_url)
|
|
46
46
|
@src = src_url
|
|
47
47
|
self.attrs ||= {}
|
|
48
|
-
attrs[
|
|
48
|
+
attrs["src"] = src_url
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# Update the alt text
|
|
52
52
|
def alt=(alt_text)
|
|
53
53
|
@alt = alt_text
|
|
54
54
|
self.attrs ||= {}
|
|
55
|
-
attrs[
|
|
55
|
+
attrs["alt"] = alt_text
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Update the title (tooltip)
|
|
59
59
|
def title=(title_text)
|
|
60
60
|
@title = title_text
|
|
61
61
|
self.attrs ||= {}
|
|
62
|
-
attrs[
|
|
62
|
+
attrs["title"] = title_text
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# Update the width
|
|
66
66
|
def width=(value)
|
|
67
67
|
@width = value
|
|
68
68
|
self.attrs ||= {}
|
|
69
|
-
attrs[
|
|
69
|
+
attrs["width"] = value
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
# Update the height
|
|
73
73
|
def height=(value)
|
|
74
74
|
@height = value
|
|
75
75
|
self.attrs ||= {}
|
|
76
|
-
attrs[
|
|
76
|
+
attrs["height"] = value
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# Update dimensions (width and height)
|
|
@@ -90,13 +90,22 @@ module Prosereflect
|
|
|
90
90
|
alt: alt,
|
|
91
91
|
title: title,
|
|
92
92
|
width: width,
|
|
93
|
-
height: height
|
|
93
|
+
height: height,
|
|
94
94
|
}.compact
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
def to_h
|
|
98
|
+
hash = super
|
|
99
|
+
if hash["attrs"]
|
|
100
|
+
%w[title width height].each { |k| hash["attrs"].delete(k) if hash["attrs"][k].nil? }
|
|
101
|
+
hash.delete("attrs") if hash["attrs"].empty?
|
|
102
|
+
end
|
|
103
|
+
hash
|
|
104
|
+
end
|
|
105
|
+
|
|
97
106
|
# Override content-related methods since images don't have content
|
|
98
107
|
def add_child(*)
|
|
99
|
-
raise NotImplementedError,
|
|
108
|
+
raise NotImplementedError, "Image nodes cannot have children"
|
|
100
109
|
end
|
|
101
110
|
|
|
102
111
|
def content
|
|
@@ -104,23 +113,23 @@ module Prosereflect
|
|
|
104
113
|
end
|
|
105
114
|
|
|
106
115
|
def src
|
|
107
|
-
@src || attrs&.[](
|
|
116
|
+
@src || attrs&.[]("src")
|
|
108
117
|
end
|
|
109
118
|
|
|
110
119
|
def alt
|
|
111
|
-
@alt || attrs&.[](
|
|
120
|
+
@alt || attrs&.[]("alt")
|
|
112
121
|
end
|
|
113
122
|
|
|
114
123
|
def title
|
|
115
|
-
@title || attrs&.[](
|
|
124
|
+
@title || attrs&.[]("title")
|
|
116
125
|
end
|
|
117
126
|
|
|
118
127
|
def width
|
|
119
|
-
@width || attrs&.[](
|
|
128
|
+
@width || attrs&.[]("width")
|
|
120
129
|
end
|
|
121
130
|
|
|
122
131
|
def height
|
|
123
|
-
@height || attrs&.[](
|
|
132
|
+
@height || attrs&.[]("height")
|
|
124
133
|
end
|
|
125
134
|
end
|
|
126
135
|
end
|