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
@@ -5,8 +5,29 @@ require_relative 'paragraph'
5
5
 
6
6
  module Prosereflect
7
7
  class TableCell < Node
8
+ PM_TYPE = 'table_cell'
9
+
10
+ attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
11
+
12
+ key_value do
13
+ map 'type', to: :type, render_default: true
14
+ map 'content', to: :content
15
+ map 'attrs', to: :attrs
16
+ end
17
+
18
+ def initialize(attributes = {})
19
+ attributes[:content] ||= []
20
+ super
21
+ end
22
+
23
+ def self.create(attrs = nil)
24
+ new(type: PM_TYPE, attrs: attrs, content: [])
25
+ end
26
+
8
27
  def paragraphs
9
- content.select { |node| node.type == 'paragraph' }
28
+ return [] unless content
29
+
30
+ content.select { |node| node.is_a?(Paragraph) }
10
31
  end
11
32
 
12
33
  def text_content
@@ -17,13 +38,6 @@ module Prosereflect
17
38
  text_content.split("\n").map(&:strip).reject(&:empty?)
18
39
  end
19
40
 
20
- # Create a new table cell
21
- def self.create(attrs = nil)
22
- cell = new({ 'type' => 'table_cell', 'content' => [] })
23
- cell.instance_variable_set(:@attrs, attrs) if attrs
24
- cell
25
- end
26
-
27
41
  # Add a paragraph to the cell
28
42
  def add_paragraph(text = nil)
29
43
  paragraph = Paragraph.create
@@ -33,5 +47,16 @@ module Prosereflect
33
47
  add_child(paragraph)
34
48
  paragraph
35
49
  end
50
+
51
+ # Override to_h to handle empty content and attributes properly
52
+ def to_h
53
+ result = super
54
+ result['content'] ||= []
55
+ if result['attrs']
56
+ result['attrs'] = result['attrs'].is_a?(Hash) && result['attrs'][:attrs] ? result['attrs'][:attrs] : result['attrs']
57
+ result.delete('attrs') if result['attrs'].empty?
58
+ end
59
+ result
60
+ end
36
61
  end
37
62
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'table_cell'
4
+
5
+ module Prosereflect
6
+ # TableHeader class represents a header cell in a table (<th> tag).
7
+ # It inherits from TableCell but adds header-specific attributes.
8
+ class TableHeader < TableCell
9
+ PM_TYPE = 'table_header'
10
+
11
+ attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
12
+ attribute :scope, :string # row, col, rowgroup, or colgroup
13
+ attribute :abbr, :string # abbreviated version of content
14
+ attribute :colspan, :integer # number of columns this header spans
15
+
16
+ key_value do
17
+ map 'type', to: :type, render_default: true
18
+ map 'content', to: :content
19
+ map 'attrs', to: :attrs
20
+ end
21
+
22
+ def initialize(attributes = {})
23
+ attributes[:content] ||= []
24
+ super
25
+ end
26
+
27
+ def self.create(attrs = nil)
28
+ new(type: PM_TYPE, attrs: attrs, content: [])
29
+ end
30
+
31
+ # Add text to the last paragraph, or create a new one if none exists
32
+ def add_text(text, marks = nil)
33
+ last_paragraph = content&.last
34
+ last_paragraph = add_paragraph if !last_paragraph || !last_paragraph.is_a?(Paragraph)
35
+ last_paragraph.add_text(text, marks)
36
+ self
37
+ end
38
+
39
+ # Set the scope of the header (row, col, rowgroup, or colgroup)
40
+ def scope=(scope_value)
41
+ return unless %w[row col rowgroup colgroup].include?(scope_value)
42
+
43
+ self.attrs ||= {}
44
+ attrs['scope'] = scope_value
45
+ end
46
+
47
+ def scope
48
+ attrs&.[]('scope')
49
+ end
50
+
51
+ # Set abbreviated version of the header content
52
+ def abbr=(abbr_text)
53
+ self.attrs ||= {}
54
+ attrs['abbr'] = abbr_text
55
+ end
56
+
57
+ def abbr
58
+ attrs&.[]('abbr')
59
+ end
60
+
61
+ # Set the number of columns this header spans
62
+ def colspan=(span)
63
+ return unless span.to_i.positive?
64
+
65
+ self.attrs ||= {}
66
+ attrs['colspan'] = span.to_i
67
+ end
68
+
69
+ def colspan
70
+ attrs&.[]('colspan')
71
+ end
72
+
73
+ # Get header attributes as a hash
74
+ def header_attributes
75
+ {
76
+ scope: scope,
77
+ abbr: abbr,
78
+ colspan: colspan
79
+ }.compact
80
+ end
81
+
82
+ # Override to_h to exclude nil attributes
83
+ def to_h
84
+ result = super
85
+ if result['attrs']
86
+ result['attrs'].reject! { |_, v| v.nil? }
87
+ result.delete('attrs') if result['attrs'].empty?
88
+ end
89
+ result
90
+ end
91
+ end
92
+ end
@@ -5,20 +5,32 @@ require_relative 'table_cell'
5
5
 
6
6
  module Prosereflect
7
7
  class TableRow < Node
8
- def cells
9
- content.select { |node| node.type == 'table_cell' }
8
+ PM_TYPE = 'table_row'
9
+
10
+ attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
11
+
12
+ key_value do
13
+ map 'type', to: :type, render_default: true
14
+ map 'content', to: :content
15
+ map 'attrs', to: :attrs
16
+ end
17
+
18
+ def initialize(opts = {})
19
+ opts[:content] ||= []
20
+ super
10
21
  end
11
22
 
12
- # Create a new table row
13
23
  def self.create(attrs = nil)
14
- row = new({ 'type' => 'table_row', 'content' => [] })
15
- row.instance_variable_set(:@attrs, attrs) if attrs
16
- row
24
+ new(type: PM_TYPE, attrs: attrs, content: [])
25
+ end
26
+
27
+ def cells
28
+ content || []
17
29
  end
18
30
 
19
31
  # Add a cell to the row
20
- def add_cell(content_text = nil)
21
- cell = TableCell.create
32
+ def add_cell(content_text = nil, attrs: nil)
33
+ cell = TableCell.create(attrs)
22
34
 
23
35
  if content_text
24
36
  paragraph = cell.add_paragraph
@@ -28,5 +40,16 @@ module Prosereflect
28
40
  add_child(cell)
29
41
  cell
30
42
  end
43
+
44
+ # Override to_h to handle empty content and attributes properly
45
+ def to_h
46
+ result = super
47
+ result['content'] ||= []
48
+ if result['attrs']
49
+ result['attrs'] = result['attrs'].is_a?(Hash) && result['attrs'][:attrs] ? result['attrs'][:attrs] : result['attrs']
50
+ result.delete('attrs') if result['attrs'].empty?
51
+ end
52
+ result
53
+ end
31
54
  end
32
55
  end
@@ -4,33 +4,29 @@ require_relative 'node'
4
4
 
5
5
  module Prosereflect
6
6
  class Text < Node
7
- attr_reader :text
8
- attr_accessor :marks
7
+ PM_TYPE = 'text'
9
8
 
10
- def initialize(data = {})
11
- super
12
- @text = data['text'] || ''
13
- end
9
+ attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
10
+ attribute :text, :string, default: ''
14
11
 
15
- def text_content
16
- @text || ''
12
+ key_value do
13
+ map 'type', to: :type, render_default: true
14
+ map 'text', to: :text
15
+ map 'marks', to: :marks
17
16
  end
18
17
 
19
- def text_content_with_breaks
20
- @text || ''
18
+ def self.create(text = '', marks = nil)
19
+ new(text: text, marks: marks)
21
20
  end
22
21
 
23
- # Create a new text node
24
- def self.create(text, marks = nil)
25
- node = new({ 'type' => 'text', 'text' => text })
26
- node.instance_variable_set(:@marks, marks) if marks
27
- node
22
+ def text_content
23
+ text || ''
28
24
  end
29
25
 
30
- # Convert to hash representation
26
+ # Override the to_h method to include the text attribute
31
27
  def to_h
32
28
  result = super
33
- result['text'] = @text if @text
29
+ result['text'] = text
34
30
  result
35
31
  end
36
32
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+
5
+ module Prosereflect
6
+ # User class represents a user mention in ProseMirror.
7
+ class User < Node
8
+ PM_TYPE = 'user'
9
+
10
+ attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
11
+ attribute :id, :string
12
+ attribute :attrs, :hash
13
+
14
+ key_value do
15
+ map 'type', to: :type, render_default: true
16
+ map 'attrs', to: :attrs
17
+ map 'content', to: :content
18
+ end
19
+
20
+ def initialize(attributes = {})
21
+ attributes[:content] = []
22
+ super
23
+
24
+ return unless attributes[:attrs]
25
+
26
+ @id = attributes[:attrs]['id']
27
+ self.attrs = { 'id' => @id }
28
+ end
29
+
30
+ def self.create(attrs = nil)
31
+ new(attrs: attrs)
32
+ end
33
+
34
+ # Update the user ID
35
+ def id=(user_id)
36
+ @id = user_id
37
+ self.attrs ||= {}
38
+ attrs['id'] = user_id
39
+ end
40
+
41
+ def id
42
+ @id || attrs&.[]('id')
43
+ end
44
+
45
+ # Override content-related methods since user mentions don't have content
46
+ def add_child(*)
47
+ raise NotImplementedError, 'User mention nodes cannot have children'
48
+ end
49
+
50
+ def content
51
+ []
52
+ end
53
+
54
+ def to_h
55
+ hash = super
56
+ hash['attrs'] = {
57
+ 'id' => id
58
+ }
59
+ hash['content'] = []
60
+ hash
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prosereflect
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.1'
5
5
  end
data/lib/prosereflect.rb CHANGED
@@ -2,14 +2,20 @@
2
2
 
3
3
  require_relative 'prosereflect/version'
4
4
  require_relative 'prosereflect/node'
5
+ require_relative 'prosereflect/mark'
6
+ require_relative 'prosereflect/attribute'
5
7
  require_relative 'prosereflect/text'
6
8
  require_relative 'prosereflect/paragraph'
7
9
  require_relative 'prosereflect/hard_break'
8
10
  require_relative 'prosereflect/table'
9
11
  require_relative 'prosereflect/table_row'
10
12
  require_relative 'prosereflect/table_cell'
13
+ require_relative 'prosereflect/heading'
11
14
  require_relative 'prosereflect/document'
12
15
  require_relative 'prosereflect/parser'
16
+ require_relative 'prosereflect/input/html'
17
+ require_relative 'prosereflect/output/html'
18
+ require_relative 'prosereflect/user'
13
19
 
14
20
  module Prosereflect
15
21
  class Error < StandardError; end
data/prosereflect.gemspec CHANGED
@@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.require_paths = ['lib']
31
31
 
32
32
  spec.add_dependency 'lutaml-model', '~> 0.7'
33
+ spec.add_dependency 'nokogiri', '~> 1.18'
33
34
  end