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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4932ddc045360d00a528353acb8c741642588d3c0d180e62752d4090f47d28d
4
- data.tar.gz: 646f972a327d8a8a735904bc31f64f2778232e077e6b1fcf6e888b1d6452ac25
3
+ metadata.gz: b84ac8ec1c9f76d0998f9b1be06b156f7ea6788f9c1d82fd2b2a34ef328263e0
4
+ data.tar.gz: 82c2549e3ae7a86cc6295e3b2230899638489743e4fa6d040a2a3f7fddbf8ffb
5
5
  SHA512:
6
- metadata.gz: fd539de5ef42d23b9d1cb4ab07a6dc2f6ea0466e02230016b09ce6f6c90dc6ab947ceaa82ee5f0058030cb0fee82484d6a676ad5841b8d6207d45dec717f785a
7
- data.tar.gz: 38e6f185405e0dd9fdd56aa9821253442f545aac7944c54e909a6e5c5ee430c7d610caac53d4d32e4de1fd5ebb7b125822e1465ce3d01e8aa745621efaae603e
6
+ metadata.gz: a7f45b590785499274eb6112a961e131af7af6a5c679a388eaf3bd01805a731f572fbf7d5efa938f5466e359e2c3dc81953816164ac3a3c0585ab9e98b97a220
7
+ data.tar.gz: dd99eed85e651b903869b53a09866a550e71b5fd29efbf8fa380b6f35bee33c2189675ca1897d10f11400975a45dbb13d798fef916a466b61181a9046fee5ff2
data/.rubocop_todo.yml CHANGED
@@ -76,22 +76,31 @@ Metrics/AbcSize:
76
76
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
77
77
  # AllowedMethods: refine
78
78
  Metrics/BlockLength:
79
- Max: 201
79
+ Exclude:
80
+ - 'spec/prosereflect/input/html_spec.rb'
81
+ Max: 500
82
+
83
+ # Offense count: 3
84
+ Metrics/ClassLength:
85
+ Exclude:
86
+ - 'lib/prosereflect/input/html.rb'
87
+ - 'lib/prosereflect/output/html.rb'
88
+ - 'lib/prosereflect/node.rb'
80
89
 
81
90
  # Offense count: 4
82
91
  # Configuration parameters: AllowedMethods, AllowedPatterns.
83
92
  Metrics/CyclomaticComplexity:
84
- Max: 30
93
+ Max: 50
85
94
 
86
95
  # Offense count: 6
87
96
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
88
97
  Metrics/MethodLength:
89
- Max: 47
98
+ Max: 90
90
99
 
91
100
  # Offense count: 3
92
101
  # Configuration parameters: AllowedMethods, AllowedPatterns.
93
102
  Metrics/PerceivedComplexity:
94
- Max: 32
103
+ Max: 35
95
104
 
96
105
  # Offense count: 9
97
106
  # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
@@ -129,6 +138,16 @@ Style/Documentation:
129
138
  - 'lib/prosereflect/table_cell.rb'
130
139
  - 'lib/prosereflect/table_row.rb'
131
140
  - 'lib/prosereflect/text.rb'
141
+ - 'lib/prosereflect/heading.rb'
142
+ - 'lib/prosereflect/attribute.rb'
143
+ - 'lib/prosereflect/attribute/base.rb'
144
+ - 'lib/prosereflect/attribute/bold.rb'
145
+ - 'lib/prosereflect/attribute/href.rb'
146
+ - 'lib/prosereflect/attribute/id.rb'
147
+ - 'lib/prosereflect/input/html.rb'
148
+ - 'lib/prosereflect/output/html.rb'
149
+ - 'lib/prosereflect/mark.rb'
150
+ - 'lib/prosereflect/mark/base.rb'
132
151
 
133
152
  # Offense count: 1
134
153
  # This cop supports unsafe autocorrection (--autocorrect-all).
data/README.adoc CHANGED
@@ -80,7 +80,7 @@ tables = document.tables
80
80
  paragraphs = document.paragraphs
81
81
 
82
82
  # Access the first table
83
- first_table = document.first_table
83
+ first_table = document.find_first('table')
84
84
 
85
85
  # Access header row and data rows in a table
86
86
  header = first_table.header_row
@@ -119,7 +119,97 @@ tables = document.find_all('table')
119
119
  text_nodes = document.find_all('text')
120
120
 
121
121
  # Find child nodes of a specific type
122
- table_cells = table.find_children('table_cell')
122
+ table_cells = table.find_children(TableCell)
123
+ ----
124
+
125
+ === HTML Conversion
126
+
127
+ The gem provides functionality to convert between HTML and ProseMirror document models.
128
+
129
+ ==== From HTML
130
+
131
+ [source,ruby]
132
+ ----
133
+ require 'prosereflect'
134
+
135
+ # Parse from HTML string
136
+ html_content = '<p>This is a <strong>bold</strong> text in a paragraph.</p>'
137
+ document = Prosereflect::Input::Html.parse(html_content)
138
+
139
+ # Access the document structure
140
+ paragraph = document.paragraphs.first
141
+ text_content = paragraph.text_content # "This is a bold text in a paragraph."
142
+ ----
143
+
144
+ ==== User Mentions
145
+
146
+ The gem supports user mentions in documents, which can be useful for social features or collaborative editing.
147
+
148
+ [source,ruby]
149
+ ----
150
+ # Create a document with user mentions
151
+ document = Prosereflect::Document.create
152
+ paragraph = document.add_paragraph('Hello ')
153
+
154
+ # Add a user mention
155
+ user = Prosereflect::User.new
156
+ user.id = '123'
157
+ paragraph.add_child(user)
158
+
159
+ paragraph.add_text('!')
160
+
161
+ # Convert to HTML
162
+ html = Prosereflect::Output::Html.convert(document)
163
+ # => "<p>Hello <user-mention data-id=\"123\"></user-mention>!</p>"
164
+
165
+ # Parse HTML with user mentions
166
+ html_content = '<p>Hello <user-mention data-id="123"></user-mention>!</p>'
167
+ document = Prosereflect::Input::Html.parse(html_content)
168
+
169
+ # Access user mentions
170
+ user_mentions = document.find_all('user')
171
+ first_user = user_mentions.first
172
+ user_id = first_user.id # => "123"
173
+ ----
174
+
175
+ User mentions are represented as `<user-mention>` elements in HTML with a `data-id` attribute containing the user's identifier. When parsing HTML, these elements are converted to `User` nodes in the document model.
176
+
177
+ Common use cases:
178
+ - Mentioning users in comments or messages
179
+ - Tagging users in collaborative documents
180
+ - Tracking user references in content
181
+
182
+ ==== To HTML
183
+
184
+ [source,ruby]
185
+ ----
186
+ require 'prosereflect'
187
+
188
+ # Create a document
189
+ document = Prosereflect::Document.create
190
+ paragraph = document.add_paragraph('Plain text')
191
+ paragraph.add_text(' with bold', [Prosereflect::Mark::Bold.new])
192
+
193
+ # Convert to HTML
194
+ html = Prosereflect::Output::Html.convert(document)
195
+ # => "<html><body><p>Plain text<strong> with bold</strong></p></body></html>"
196
+ ----
197
+
198
+ ==== Round-trip Conversion
199
+
200
+ [source,ruby]
201
+ ----
202
+ # Start with HTML
203
+ original_html = '<p>This is <em>styled</em> text.</p>'
204
+
205
+ # Convert to document model
206
+ document = Prosereflect::Input::Html.parse(original_html)
207
+
208
+ # Modify the document if needed
209
+ document.paragraphs.first.add_text(' with additions')
210
+
211
+ # Convert back to HTML
212
+ modified_html = Prosereflect::Output::Html.convert(document)
123
213
  ----
124
214
 
125
215
  == Data model
@@ -145,12 +235,12 @@ objects.
145
235
  | +content |
146
236
  +-------------------+
147
237
  |
148
- +----+----+---------------------+
149
- | | |
150
- +---v---+ +---v----------+ +-------v--------+
151
- |Table | | Paragraph | | Text |
152
- | | | | | |
153
- +---+---+ +--------------+ +----------------+
238
+ +----+----+---------------------+-------------+
239
+ | | | |
240
+ +---v---+ +---v----------+ +-------v--------+ +-v-----+
241
+ |Table | | Paragraph | | Text | | User |
242
+ | | | | | | | |
243
+ +---+---+ +--------------+ +----------------+ +-------+
154
244
  |
155
245
  |
156
246
  +---v-----------+
@@ -193,14 +283,105 @@ Represents a text node.
193
283
 
194
284
  `text`:: The text content of the node
195
285
 
286
+ === User
287
+
288
+ Represents a user mention in the document.
289
+
290
+ `id`:: The unique identifier of the referenced user
291
+ `type`:: Always set to "user"
292
+ `content`:: Always empty (user mentions cannot have child nodes)
293
+
196
294
  === Table
197
295
 
198
296
  Represents a table structure.
199
297
 
200
- `rows`:: All table rows
201
- `header_row`:: The first row (assumed to be the header)
202
- `data_rows`:: All rows except the header
203
- `cell_at(row_index, col_index)`:: Access a specific cell by position
298
+ `rows`:: Collection of table rows
299
+ `header_row`:: First row if it contains header cells
300
+ `data_rows`:: All non-header rows
301
+
302
+ === Heading
303
+
304
+ Represents a heading element (h1-h6).
305
+
306
+ `level`:: The heading level (1-6)
307
+ `text_content`:: Returns the combined text content of all child text nodes
308
+ `content`:: Collection of child nodes (text, styled text, etc.)
309
+
310
+ === Image
311
+
312
+ Represents an image element.
313
+
314
+ `src`:: The image source URL
315
+ `alt`:: Alternative text description
316
+ `title`:: Image tooltip text
317
+ `width`:: Image width in pixels
318
+ `height`:: Image height in pixels
319
+
320
+ === HorizontalRule
321
+
322
+ Represents a horizontal rule (hr) element.
323
+
324
+ `style`:: Border style (solid, dashed, dotted)
325
+ `width`:: Rule width (px or %)
326
+ `thickness`:: Border thickness in pixels
327
+
328
+ === BulletList
329
+
330
+ Represents an unordered list.
331
+
332
+ `bullet_style`:: List style type (disc, circle, square)
333
+ `items`:: Collection of list items
334
+
335
+ === OrderedList
336
+
337
+ Represents an ordered list.
338
+
339
+ `start`:: Starting number for the list
340
+ `items`:: Collection of list items
341
+
342
+ === ListItem
343
+
344
+ Represents a list item within ordered or unordered lists.
345
+
346
+ `content`:: Collection of child nodes (can contain paragraphs, nested lists, etc.)
347
+ `text_content`:: Returns the combined text content
348
+
349
+ === Blockquote
350
+
351
+ Represents a blockquote element.
352
+
353
+ `citation`:: Optional citation URL
354
+ `blocks`:: Collection of content blocks within the quote
355
+
356
+ === CodeBlockWrapper
357
+
358
+ Container for code blocks with additional attributes.
359
+
360
+ `line_numbers`:: Whether to display line numbers
361
+ `highlight_lines`:: Array of line numbers to highlight
362
+ `code_blocks`:: Collection of code blocks
363
+
364
+ === CodeBlock
365
+
366
+ Represents a code block with syntax highlighting.
367
+
368
+ `content`:: The code content
369
+ `language`:: Programming language for syntax highlighting
370
+
371
+ === Mark
372
+
373
+ Base class for text formatting marks.
374
+
375
+ ==== Available Mark Types
376
+
377
+ `Bold`:: Bold text formatting
378
+ `Italic`:: Italic text formatting
379
+ `Code`:: Inline code formatting
380
+ `Link`:: Hyperlink with href attribute
381
+ `Strike`:: Strikethrough text
382
+ `Subscript`:: Subscript text
383
+ `Superscript`:: Superscript text
384
+ `Underline`:: Underlined text
204
385
 
205
386
  === TableRow
206
387
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../attribute'
4
+
5
+ module Prosereflect
6
+ module Attribute
7
+ class Base < Lutaml::Model::Serializable
8
+ PM_TYPE = 'attribute'
9
+
10
+ attribute :type, :string, default: lambda {
11
+ begin
12
+ self.class.const_get(:PM_TYPE)
13
+ rescue StandardError
14
+ 'attribute'
15
+ end
16
+ }
17
+ attribute :value, :string
18
+
19
+ key_value do
20
+ map 'type', to: :type, render_default: true
21
+ map 'value', to: :value
22
+ end
23
+
24
+ def self.create(type, value)
25
+ new(type: type, value: value)
26
+ end
27
+
28
+ # Convert to hash for serialization
29
+ def to_h
30
+ { type => value }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Prosereflect
6
+ module Attribute
7
+ class Bold < Base
8
+ PM_TYPE = 'bold'
9
+
10
+ def initialize(options = {})
11
+ super
12
+ self.type = 'bold'
13
+ end
14
+
15
+ def attrs
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Prosereflect
6
+ module Attribute
7
+ class Href < Base
8
+ PM_TYPE = 'href'
9
+
10
+ attribute :type, :string, default: -> { PM_TYPE }
11
+ attribute :href, :string
12
+
13
+ def initialize(options = {})
14
+ if options.is_a?(String)
15
+ super()
16
+ self.value = options
17
+ else
18
+ super
19
+ self.value = options[:href] if options[:href]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Prosereflect
6
+ module Attribute
7
+ class Id < Base
8
+ PM_TYPE = 'id'
9
+
10
+ attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
11
+ attribute :id, :string
12
+
13
+ key_value do
14
+ map 'type', to: :type, render_default: true
15
+ map 'id', to: :id
16
+ end
17
+
18
+ # Convert to hash for serialization
19
+ def to_h
20
+ { 'id' => id }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+
5
+ module Prosereflect
6
+ module Attribute
7
+ end
8
+ end
9
+
10
+ require_relative 'attribute/base'
11
+ require_relative 'attribute/href'
12
+ require_relative 'attribute/id'
13
+ require_relative 'attribute/bold'
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+ require_relative 'paragraph'
5
+
6
+ module Prosereflect
7
+ # It can contain other block-level content like paragraphs, lists, etc.
8
+ class Blockquote < Node
9
+ PM_TYPE = 'blockquote'
10
+
11
+ attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
12
+ attribute :citation, :string
13
+ attribute :attrs, :hash
14
+
15
+ key_value do
16
+ map 'type', to: :type, render_default: true
17
+ map 'content', to: :content
18
+ map 'attrs', to: :attrs
19
+ end
20
+
21
+ def initialize(attributes = {})
22
+ attributes[:content] ||= []
23
+ super
24
+ end
25
+
26
+ def self.create(attrs = nil)
27
+ new(attrs: attrs, content: [])
28
+ end
29
+
30
+ # Get all content blocks within the blockquote
31
+ def blocks
32
+ return [] unless content
33
+
34
+ content
35
+ end
36
+
37
+ # Add a content block to the blockquote
38
+ def add_block(content)
39
+ add_child(content)
40
+ end
41
+
42
+ # Add multiple content blocks at once
43
+ def add_blocks(blocks_content)
44
+ blocks_content.each do |block_content|
45
+ add_block(block_content)
46
+ end
47
+ end
48
+
49
+ # Get block at specific position
50
+ def block_at(index)
51
+ return nil if index.negative?
52
+
53
+ blocks[index]
54
+ end
55
+
56
+ # Update citation/source for the blockquote
57
+ def citation=(source)
58
+ self.attrs ||= {}
59
+ attrs['citation'] = source
60
+ end
61
+
62
+ # Get citation/source of the blockquote
63
+ def citation
64
+ attrs&.[]('citation')
65
+ end
66
+
67
+ # Check if blockquote has a citation
68
+ def citation?
69
+ !citation.nil? && !citation.empty?
70
+ end
71
+
72
+ # Remove citation
73
+ def remove_citation
74
+ self.attrs ||= {}
75
+ attrs.delete('citation')
76
+ end
77
+
78
+ def add_paragraph(text)
79
+ paragraph = Paragraph.new
80
+ paragraph.add_text(text)
81
+ add_child(paragraph)
82
+ paragraph
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'node'
4
+ require_relative 'list_item'
5
+
6
+ module Prosereflect
7
+ # BulletList class represents an unordered list in ProseMirror.
8
+ class BulletList < Node
9
+ PM_TYPE = 'bullet_list'
10
+
11
+ attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
12
+ attribute :bullet_style, :string
13
+ attribute :attrs, :hash
14
+
15
+ key_value do
16
+ map 'type', to: :type, render_default: true
17
+ map 'attrs', to: :attrs
18
+ map 'content', to: :content
19
+ end
20
+
21
+ def initialize(attributes = {})
22
+ attributes[:content] ||= []
23
+ attributes[:attrs] ||= { 'bullet_style' => nil }
24
+ super
25
+ end
26
+
27
+ def self.create(attrs = nil)
28
+ new(attrs: attrs)
29
+ end
30
+
31
+ def bullet_style=(value)
32
+ @bullet_style = value
33
+ self.attrs ||= {}
34
+ attrs['bullet_style'] = value
35
+ end
36
+
37
+ def bullet_style
38
+ @bullet_style || attrs&.[]('bullet_style')
39
+ end
40
+
41
+ def add_item(text)
42
+ item = ListItem.new
43
+ item.add_paragraph(text)
44
+ add_child(item)
45
+ item
46
+ end
47
+
48
+ def items
49
+ return [] unless content
50
+
51
+ content
52
+ end
53
+
54
+ # Add multiple items at once
55
+ def add_items(items_content)
56
+ items_content.each do |item_content|
57
+ add_item(item_content)
58
+ end
59
+ end
60
+
61
+ # Get item at specific position
62
+ def item_at(index)
63
+ return nil if index.negative?
64
+
65
+ items[index]
66
+ end
67
+
68
+ # Get text content with proper formatting
69
+ def text_content
70
+ return '' unless content
71
+
72
+ content.map { |item| item.respond_to?(:text_content) ? item.text_content : '' }.join("\n")
73
+ end
74
+
75
+ # Override to_h to exclude empty attrs
76
+ def to_h
77
+ hash = super
78
+ hash['attrs'] ||= {}
79
+ hash['attrs']['bullet_style'] = bullet_style
80
+ hash
81
+ end
82
+ end
83
+ end