prosereflect 0.1.0 → 0.1.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +23 -4
  3. data/README.adoc +193 -12
  4. data/lib/prosereflect/attribute/base.rb +34 -0
  5. data/lib/prosereflect/attribute/bold.rb +20 -0
  6. data/lib/prosereflect/attribute/href.rb +24 -0
  7. data/lib/prosereflect/attribute/id.rb +24 -0
  8. data/lib/prosereflect/attribute.rb +13 -0
  9. data/lib/prosereflect/blockquote.rb +85 -0
  10. data/lib/prosereflect/bullet_list.rb +83 -0
  11. data/lib/prosereflect/code_block.rb +135 -0
  12. data/lib/prosereflect/code_block_wrapper.rb +66 -0
  13. data/lib/prosereflect/document.rb +99 -24
  14. data/lib/prosereflect/hard_break.rb +11 -9
  15. data/lib/prosereflect/heading.rb +64 -0
  16. data/lib/prosereflect/horizontal_rule.rb +70 -0
  17. data/lib/prosereflect/image.rb +126 -0
  18. data/lib/prosereflect/input/html.rb +505 -0
  19. data/lib/prosereflect/list_item.rb +65 -0
  20. data/lib/prosereflect/mark/base.rb +49 -0
  21. data/lib/prosereflect/mark/bold.rb +15 -0
  22. data/lib/prosereflect/mark/code.rb +14 -0
  23. data/lib/prosereflect/mark/italic.rb +15 -0
  24. data/lib/prosereflect/mark/link.rb +18 -0
  25. data/lib/prosereflect/mark/strike.rb +15 -0
  26. data/lib/prosereflect/mark/subscript.rb +15 -0
  27. data/lib/prosereflect/mark/superscript.rb +15 -0
  28. data/lib/prosereflect/mark/underline.rb +15 -0
  29. data/lib/prosereflect/mark.rb +11 -0
  30. data/lib/prosereflect/node.rb +181 -32
  31. data/lib/prosereflect/ordered_list.rb +85 -0
  32. data/lib/prosereflect/output/html.rb +374 -0
  33. data/lib/prosereflect/paragraph.rb +26 -15
  34. data/lib/prosereflect/parser.rb +111 -24
  35. data/lib/prosereflect/table.rb +40 -9
  36. data/lib/prosereflect/table_cell.rb +33 -8
  37. data/lib/prosereflect/table_header.rb +92 -0
  38. data/lib/prosereflect/table_row.rb +31 -8
  39. data/lib/prosereflect/text.rb +13 -17
  40. data/lib/prosereflect/user.rb +63 -0
  41. data/lib/prosereflect/version.rb +1 -1
  42. data/lib/prosereflect.rb +6 -0
  43. data/prosereflect.gemspec +1 -0
  44. data/spec/prosereflect/document_spec.rb +436 -36
  45. data/spec/prosereflect/hard_break_spec.rb +218 -22
  46. data/spec/prosereflect/input/html_spec.rb +797 -0
  47. data/spec/prosereflect/node_spec.rb +258 -89
  48. data/spec/prosereflect/output/html_spec.rb +369 -0
  49. data/spec/prosereflect/paragraph_spec.rb +424 -49
  50. data/spec/prosereflect/parser_spec.rb +304 -91
  51. data/spec/prosereflect/table_cell_spec.rb +268 -57
  52. data/spec/prosereflect/table_row_spec.rb +210 -40
  53. data/spec/prosereflect/table_spec.rb +392 -61
  54. data/spec/prosereflect/text_spec.rb +206 -48
  55. data/spec/prosereflect/user_spec.rb +73 -0
  56. data/spec/prosereflect_spec.rb +5 -0
  57. data/spec/support/shared_examples.rb +44 -15
  58. metadata +47 -3
  59. data/debug_loading.rb +0 -34
@@ -0,0 +1,505 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require_relative '../document'
5
+ require_relative '../paragraph'
6
+ require_relative '../text'
7
+ require_relative '../table'
8
+ require_relative '../table_row'
9
+ require_relative '../table_cell'
10
+ require_relative '../table_header'
11
+ require_relative '../hard_break'
12
+ require_relative '../mark/bold'
13
+ require_relative '../mark/italic'
14
+ require_relative '../mark/code'
15
+ require_relative '../mark/link'
16
+ require_relative '../mark/strike'
17
+ require_relative '../mark/subscript'
18
+ require_relative '../mark/superscript'
19
+ require_relative '../mark/underline'
20
+ require_relative '../attribute/href'
21
+ require_relative '../ordered_list'
22
+ require_relative '../bullet_list'
23
+ require_relative '../list_item'
24
+ require_relative '../blockquote'
25
+ require_relative '../horizontal_rule'
26
+ require_relative '../image'
27
+ require_relative '../code_block_wrapper'
28
+ require_relative '../code_block'
29
+ require_relative '../heading'
30
+ require_relative '../user'
31
+
32
+ module Prosereflect
33
+ module Input
34
+ class Html
35
+ class << self
36
+ # Parse HTML content and return a Prosereflect::Document
37
+ def parse(html)
38
+ html_doc = Nokogiri::HTML(html)
39
+ document = Document.create # Use create instead of new to initialize content array
40
+
41
+ content_node = html_doc.at_css('body') || html_doc.root
42
+
43
+ # Process all child nodes
44
+ process_node_children(content_node, document)
45
+
46
+ document
47
+ end
48
+
49
+ private
50
+
51
+ # Process children of a node and add to parent
52
+ def process_node_children(html_node, parent_node)
53
+ return unless html_node&.children
54
+
55
+ html_node.children.each do |child|
56
+ node = convert_node(child)
57
+
58
+ if node.is_a?(Array)
59
+ node.each { |n| parent_node.add_child(n) }
60
+ elsif node
61
+ parent_node.add_child(node)
62
+ end
63
+ end
64
+ end
65
+
66
+ # Convert an HTML node to a ProseMirror node
67
+ def convert_node(html_node)
68
+ return nil if html_node.comment? || html_node.text? && html_node.text.strip.empty?
69
+
70
+ case html_node.name
71
+ when 'text', '#text'
72
+ create_text_node(html_node)
73
+ when 'p'
74
+ create_paragraph_node(html_node)
75
+ when /^h([1-6])$/
76
+ create_heading_node(html_node, Regexp.last_match(1).to_i)
77
+ when 'br'
78
+ HardBreak.new
79
+ when 'table'
80
+ create_table_node(html_node)
81
+ when 'tr'
82
+ create_table_row_node(html_node)
83
+ when 'th', 'td'
84
+ create_table_cell_node(html_node)
85
+ when 'ol'
86
+ create_ordered_list_node(html_node)
87
+ when 'ul'
88
+ create_bullet_list_node(html_node)
89
+ when 'li'
90
+ create_list_item_node(html_node)
91
+ when 'blockquote'
92
+ create_blockquote_node(html_node)
93
+ when 'hr'
94
+ create_horizontal_rule_node(html_node)
95
+ when 'img'
96
+ create_image_node(html_node)
97
+ when 'user-mention'
98
+ create_user_node(html_node)
99
+ when 'div', 'span'
100
+ # For containers, we process their children
101
+ handle_container_node(html_node)
102
+ when 'pre'
103
+ create_code_block_wrapper(html_node)
104
+ when 'strong', 'b', 'em', 'i', 'code', 'a', 'strike', 's', 'del', 'sub', 'sup', 'u'
105
+ # For inline elements with text styling, we handle differently
106
+ handle_styled_text(html_node)
107
+ else
108
+ # Default handling for unknown elements - try to extract content
109
+ handle_container_node(html_node)
110
+ end
111
+ end
112
+
113
+ # Create a text node from HTML text
114
+ def create_text_node(html_node)
115
+ Text.new(text: html_node.text)
116
+ end
117
+
118
+ # Create a paragraph node from HTML paragraph
119
+ def create_paragraph_node(html_node)
120
+ paragraph = Paragraph.new
121
+ process_node_children(html_node, paragraph)
122
+ paragraph
123
+ end
124
+
125
+ # Create a table node from HTML table
126
+ def create_table_node(html_node)
127
+ table = Table.new
128
+
129
+ thead = html_node.at_css('thead')
130
+ thead&.css('tr')&.each do |tr|
131
+ process_table_row(tr, table, true)
132
+ end
133
+
134
+ tbody = html_node.at_css('tbody') || html_node
135
+ tbody.css('tr').each do |tr|
136
+ process_table_row(tr, table, false)
137
+ end
138
+
139
+ table
140
+ end
141
+
142
+ # Process a table row
143
+ def create_table_row_node(html_node)
144
+ row = TableRow.new
145
+ html_node.css('th, td').each do |cell|
146
+ row.add_child(create_table_cell_node(cell))
147
+ end
148
+ row
149
+ end
150
+
151
+ # Add a row to a table
152
+ def process_table_row(tr_node, table, _is_header)
153
+ row = create_table_row_node(tr_node)
154
+ table.add_child(row)
155
+ end
156
+
157
+ # Create a table cell node from HTML cell
158
+ def create_table_cell_node(html_node)
159
+ # Create either a TableHeader or TableCell based on the tag name
160
+ cell = if html_node.name == 'th'
161
+ header = TableHeader.create
162
+
163
+ # Handle header-specific attributes
164
+ header.scope = html_node['scope'] if html_node['scope']
165
+ header.abbr = html_node['abbr'] if html_node['abbr']
166
+ header.colspan = html_node['colspan'] if html_node['colspan']
167
+
168
+ header
169
+ else
170
+ TableCell.create
171
+ end
172
+
173
+ if contains_only_text_or_inline(html_node)
174
+ paragraph = Paragraph.new
175
+ process_node_children(html_node, paragraph)
176
+ cell.add_child(paragraph)
177
+ else
178
+ process_node_children(html_node, cell)
179
+ end
180
+
181
+ cell
182
+ end
183
+
184
+ # Handle a container-like node (div, span, etc.)
185
+ def handle_container_node(html_node)
186
+ # For top-level divs, process children directly
187
+ if html_node.name == 'div'
188
+ results = []
189
+ html_node.children.each do |child|
190
+ next if child.text? && child.text.strip.empty?
191
+
192
+ node = convert_node(child)
193
+ if node.is_a?(Array)
194
+ results.concat(node)
195
+ elsif node
196
+ results << node
197
+ end
198
+ end
199
+ return results if results.any?
200
+ end
201
+
202
+ if contains_only_text_or_inline(html_node)
203
+ paragraph = Paragraph.new
204
+ process_node_children(html_node, paragraph)
205
+ return paragraph
206
+ end
207
+
208
+ children = []
209
+ html_node.children.each do |child|
210
+ node = convert_node(child)
211
+ next unless node
212
+
213
+ if node.is_a?(Array)
214
+ children.concat(node)
215
+ else
216
+ children << node
217
+ end
218
+ end
219
+
220
+ children
221
+ end
222
+
223
+ # Handle styled text (bold, italic, etc.)
224
+ def handle_styled_text(html_node)
225
+ # Create mark based on the current node
226
+ mark = case html_node.name
227
+ when 'strong', 'b'
228
+ mark = Mark::Bold.new
229
+ mark.type = 'bold'
230
+ mark
231
+ when 'em', 'i'
232
+ mark = Mark::Italic.new
233
+ mark.type = 'italic'
234
+ mark
235
+ when 'code'
236
+ mark = Mark::Code.new
237
+ mark.type = 'code'
238
+ mark
239
+ when 'a'
240
+ mark = Mark::Link.new
241
+ mark.type = 'link'
242
+ mark.attrs = { 'href' => html_node['href'] } if html_node['href']
243
+ mark
244
+ when 'strike', 's', 'del'
245
+ mark = Mark::Strike.new
246
+ mark.type = 'strike'
247
+ mark
248
+ when 'sub'
249
+ mark = Mark::Subscript.new
250
+ mark.type = 'subscript'
251
+ mark
252
+ when 'sup'
253
+ mark = Mark::Superscript.new
254
+ mark.type = 'superscript'
255
+ mark
256
+ when 'u'
257
+ mark = Mark::Underline.new
258
+ mark.type = 'underline'
259
+ mark
260
+ end
261
+
262
+ return convert_node(html_node.children.first) unless mark
263
+
264
+ # If the node has children that are not just text, process them
265
+ if html_node.children.any? { |child| !child.text? }
266
+ # Process children and add the current mark to their marks
267
+ results = []
268
+ html_node.children.each do |child|
269
+ node = convert_node(child)
270
+ next unless node
271
+
272
+ if node.is_a?(Array)
273
+ node.each do |n|
274
+ n.marks = (n.raw_marks || []) + [mark]
275
+ results << n
276
+ end
277
+ else
278
+ node.marks = (node.raw_marks || []) + [mark]
279
+ results << node
280
+ end
281
+ end
282
+ results
283
+ else
284
+ # Create a text node with the mark
285
+ text = Text.new(text: html_node.text)
286
+ text.marks = [mark]
287
+ text
288
+ end
289
+ end
290
+
291
+ # Check if a node contains only text or inline elements
292
+ def contains_only_text_or_inline(node)
293
+ node.children.all? do |child|
294
+ child.text? ||
295
+ %w[strong b em i code a br span strike s del sub sup u].include?(child.name) ||
296
+ (child.element? && contains_only_text_or_inline(child))
297
+ end
298
+ end
299
+
300
+ # Create an ordered list node from HTML ol
301
+ def create_ordered_list_node(html_node)
302
+ list = OrderedList.new
303
+
304
+ # Handle start attribute
305
+ start_val = (html_node['start'] || '1').to_i
306
+ list.start = start_val
307
+
308
+ # Process list items
309
+ html_node.css('> li').each do |li|
310
+ list.add_child(create_list_item_node(li))
311
+ end
312
+
313
+ list
314
+ end
315
+
316
+ # Create a bullet list node from HTML ul
317
+ def create_bullet_list_node(html_node)
318
+ list = BulletList.new
319
+ list.bullet_style = nil
320
+
321
+ # Handle style attribute if present
322
+ if html_node['style']&.include?('list-style-type')
323
+ style = case html_node['style']
324
+ when /disc/ then 'disc'
325
+ when /circle/ then 'circle'
326
+ when /square/ then 'square'
327
+ end
328
+ list.bullet_style = style
329
+ end
330
+
331
+ process_node_children(html_node, list)
332
+ list
333
+ end
334
+
335
+ # Create a list item node from HTML li
336
+ def create_list_item_node(html_node)
337
+ item = ListItem.new
338
+
339
+ # Handle text content first
340
+ text_content = html_node.children.select { |child| child.text? || inline_element?(child) }
341
+ if text_content.any?
342
+ paragraph = Paragraph.new
343
+ text_content.each do |child|
344
+ node = convert_node(child)
345
+ paragraph.add_child(node) if node
346
+ end
347
+ item.add_content(paragraph)
348
+ end
349
+
350
+ # Handle nested content
351
+ html_node.children.reject { |child| child.text? || inline_element?(child) }.each do |child|
352
+ node = convert_node(child)
353
+ if node.is_a?(Array)
354
+ node.each { |n| item.add_content(n) }
355
+ elsif node
356
+ item.add_content(node)
357
+ end
358
+ end
359
+
360
+ item
361
+ end
362
+
363
+ # Check if a node is an inline element
364
+ def inline_element?(node)
365
+ return false unless node.element?
366
+
367
+ %w[strong b em i code a br span strike s del sub sup u].include?(node.name)
368
+ end
369
+
370
+ # Create a blockquote node from HTML blockquote
371
+ def create_blockquote_node(html_node)
372
+ quote = Blockquote.new
373
+
374
+ # Handle cite attribute if present
375
+ quote.citation = html_node['cite'] if html_node['cite']
376
+
377
+ # Process each child separately to maintain block structure
378
+ html_node.children.each do |child|
379
+ next if child.text? && child.text.strip.empty?
380
+
381
+ if child.text? || inline_element?(child)
382
+ # Wrap loose text in paragraphs
383
+ para = Paragraph.new
384
+ para.add_child(convert_node(child))
385
+ quote.add_block(para)
386
+ else
387
+ node = convert_node(child)
388
+ if node.is_a?(Array)
389
+ node.each { |n| quote.add_block(n) }
390
+ elsif node
391
+ quote.add_block(node)
392
+ end
393
+ end
394
+ end
395
+
396
+ quote
397
+ end
398
+
399
+ # Create a horizontal rule node from HTML hr
400
+ def create_horizontal_rule_node(html_node)
401
+ hr = HorizontalRule.new
402
+
403
+ # Handle style attributes if present
404
+ if html_node['style']
405
+ style = html_node['style']
406
+
407
+ # Parse border-style
408
+ hr.style = Regexp.last_match(1) if style =~ /border-style:\s*(solid|dashed|dotted)/
409
+
410
+ # Parse width
411
+ hr.width = Regexp.last_match(1) if style =~ /width:\s*(\d+(?:px|%)?)/
412
+
413
+ # Parse thickness (border-width)
414
+ hr.thickness = Regexp.last_match(1).to_i if style =~ /border-width:\s*(\d+)px/
415
+ end
416
+
417
+ hr
418
+ end
419
+
420
+ # Create an image node from HTML img
421
+ def create_image_node(html_node)
422
+ # Skip images without src
423
+ return nil unless html_node['src']
424
+
425
+ image = Image.new
426
+
427
+ # Handle required src attribute
428
+ image.src = html_node['src']
429
+
430
+ # Handle optional attributes
431
+ image.alt = html_node['alt'] if html_node['alt']
432
+ image.title = html_node['title'] if html_node['title']
433
+
434
+ # Handle dimensions
435
+ width = html_node['width']&.to_i
436
+ height = html_node['height']&.to_i
437
+ image.dimensions = [width, height] if width || height
438
+
439
+ image
440
+ end
441
+
442
+ # Create a code block wrapper from HTML pre tag
443
+ def create_code_block_wrapper(html_node)
444
+ wrapper = CodeBlockWrapper.new
445
+ wrapper.attrs = {
446
+ 'line_numbers' => false
447
+ }
448
+
449
+ code_node = html_node.at_css('code')
450
+ if code_node
451
+ block = create_code_block(code_node)
452
+ wrapper.add_child(block)
453
+ end
454
+
455
+ wrapper.to_h['attrs'] = {
456
+ 'line_numbers' => false
457
+ }
458
+ wrapper
459
+ end
460
+
461
+ # Create a code block from HTML code tag
462
+ def create_code_block(html_node)
463
+ block = CodeBlock.new
464
+ content = html_node.text.strip
465
+ language = extract_language(html_node)
466
+
467
+ block.attrs = {
468
+ 'content' => content,
469
+ 'language' => language,
470
+ 'line_numbers' => nil
471
+ }
472
+ block.content = content
473
+
474
+ block
475
+ end
476
+
477
+ def extract_language(html_node)
478
+ return nil unless html_node['class']
479
+
480
+ return unless html_node['class'] =~ /language-(\w+)/
481
+
482
+ Regexp.last_match(1)
483
+ end
484
+
485
+ # Create a heading node from HTML heading tag (h1-h6)
486
+ def create_heading_node(html_node, level)
487
+ heading = Heading.new
488
+ heading.level = level
489
+ process_node_children(html_node, heading)
490
+ heading
491
+ end
492
+
493
+ # Create a user mention node from HTML user-mention element
494
+ def create_user_node(html_node)
495
+ # Skip user mentions without data-id
496
+ return nil unless html_node['data-id']
497
+
498
+ user = User.new
499
+ user.id = html_node['data-id']
500
+ user
501
+ end
502
+ end
503
+ end
504
+ end
505
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+ require_relative 'paragraph'
5
+ require_relative 'text'
6
+ require_relative 'hard_break'
7
+
8
+ module Prosereflect
9
+ # ListItem class represents a list item in ProseMirror.
10
+ class ListItem < Node
11
+ PM_TYPE = 'list_item'
12
+
13
+ attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
14
+ attribute :attrs, :hash
15
+
16
+ key_value do
17
+ map 'type', to: :type, render_default: true
18
+ map 'attrs', to: :attrs
19
+ map 'content', to: :content
20
+ end
21
+
22
+ def initialize(attributes = {})
23
+ attributes[:content] ||= []
24
+ super
25
+ end
26
+
27
+ def self.create(attrs = nil)
28
+ new(attrs: attrs)
29
+ end
30
+
31
+ def add_paragraph(text = nil)
32
+ paragraph = Paragraph.new
33
+ paragraph.add_text(text) if text
34
+ add_child(paragraph)
35
+ paragraph
36
+ end
37
+
38
+ def add_content(content)
39
+ add_child(content)
40
+ end
41
+
42
+ # Add text to the last paragraph, or create a new one if none exists
43
+ def add_text(text, marks = nil)
44
+ last_paragraph = content&.last
45
+ last_paragraph = add_paragraph if !last_paragraph || !last_paragraph.is_a?(Paragraph)
46
+ last_paragraph.add_text(text, marks)
47
+ self
48
+ end
49
+
50
+ # Add a hard break to the last paragraph, or create a new one if none exists
51
+ def add_hard_break(marks = nil)
52
+ last_paragraph = content&.last
53
+ last_paragraph = add_paragraph if !last_paragraph || !last_paragraph.is_a?(Paragraph)
54
+ last_paragraph.add_hard_break(marks)
55
+ self
56
+ end
57
+
58
+ # Get plain text content from all nodes
59
+ def text_content
60
+ return '' unless content
61
+
62
+ content.map { |node| node.respond_to?(:text_content) ? node.text_content : '' }.join("\n").strip
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+
5
+ module Prosereflect
6
+ module Mark
7
+ class Base < Lutaml::Model::Serializable
8
+ PM_TYPE = 'mark'
9
+
10
+ attribute :type, :string, default: lambda {
11
+ begin
12
+ self.class.const_get(:PM_TYPE)
13
+ rescue StandardError
14
+ 'mark'
15
+ end
16
+ }
17
+ attribute :attrs, :hash
18
+
19
+ key_value do
20
+ map 'type', to: :type, render_default: true
21
+ map 'attrs', to: :attrs
22
+ end
23
+
24
+ def self.create(attrs = nil)
25
+ new(type: const_get(:PM_TYPE), attrs: attrs)
26
+ rescue NameError
27
+ new(type: 'mark', attrs: attrs)
28
+ end
29
+
30
+ # Convert to hash for serialization
31
+ def to_h
32
+ result = { 'type' => type }
33
+ result['attrs'] = attrs if attrs && !attrs.empty?
34
+ result
35
+ end
36
+
37
+ # Override initialize to ensure the type is set correctly
38
+ def initialize(options = {})
39
+ super(options)
40
+ # Only set the type to PM_TYPE if no type was provided in options
41
+ self.type = begin
42
+ options[:type] || self.class.const_get(:PM_TYPE)
43
+ rescue StandardError
44
+ 'mark'
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ # {
6
+ # type: "bold"
7
+ # }
8
+
9
+ module Prosereflect
10
+ module Mark
11
+ class Bold < Base
12
+ PM_TYPE = 'bold'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ # {
6
+ # type: "code"
7
+ # }
8
+ module Prosereflect
9
+ module Mark
10
+ class Code < Base
11
+ PM_TYPE = 'code'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ # {
6
+ # type: "italic"
7
+ # }
8
+
9
+ module Prosereflect
10
+ module Mark
11
+ class Italic < Base
12
+ PM_TYPE = 'italic'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ # {
6
+ # type: "link",
7
+ # attrs: {
8
+ # href: @node.attribute('href').value
9
+ # }
10
+ # }
11
+
12
+ module Prosereflect
13
+ module Mark
14
+ class Link < Base
15
+ PM_TYPE = 'link'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ # {
6
+ # type: "strike"
7
+ # }
8
+
9
+ module Prosereflect
10
+ module Mark
11
+ class Strike < Base
12
+ PM_TYPE = 'strike'
13
+ end
14
+ end
15
+ end