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.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/links.yml +97 -0
  4. data/.github/workflows/rake.yml +4 -0
  5. data/.github/workflows/release.yml +5 -0
  6. data/.gitignore +4 -0
  7. data/.rubocop.yml +19 -1
  8. data/.rubocop_todo.yml +119 -183
  9. data/CLAUDE.md +78 -0
  10. data/Gemfile +8 -4
  11. data/README.adoc +2 -0
  12. data/Rakefile +3 -3
  13. data/docs/Gemfile +10 -0
  14. data/docs/INDEX.adoc +45 -0
  15. data/docs/_advanced/index.adoc +15 -0
  16. data/docs/_advanced/schema.adoc +112 -0
  17. data/docs/_advanced/step-map.adoc +66 -0
  18. data/docs/_advanced/steps.adoc +88 -0
  19. data/docs/_advanced/test-builder.adoc +61 -0
  20. data/docs/_advanced/transform.adoc +92 -0
  21. data/docs/_config.yml +174 -0
  22. data/docs/_features/html-input.adoc +69 -0
  23. data/docs/_features/html-output.adoc +45 -0
  24. data/docs/_features/index.adoc +15 -0
  25. data/docs/_features/marks.adoc +86 -0
  26. data/docs/_features/node-types.adoc +124 -0
  27. data/docs/_features/user-mentions.adoc +47 -0
  28. data/docs/_guides/custom-nodes.adoc +107 -0
  29. data/docs/_guides/index.adoc +13 -0
  30. data/docs/_guides/round-trip-html.adoc +91 -0
  31. data/docs/_guides/serialization.adoc +109 -0
  32. data/docs/_pages/index.adoc +67 -0
  33. data/docs/_reference/document-api.adoc +49 -0
  34. data/docs/_reference/index.adoc +14 -0
  35. data/docs/_reference/node-api.adoc +79 -0
  36. data/docs/_reference/schema-api.adoc +95 -0
  37. data/docs/_reference/transform-api.adoc +77 -0
  38. data/docs/_understanding/document-model.adoc +65 -0
  39. data/docs/_understanding/fragment.adoc +52 -0
  40. data/docs/_understanding/index.adoc +14 -0
  41. data/docs/_understanding/resolved-position.adoc +53 -0
  42. data/docs/_understanding/slice.adoc +54 -0
  43. data/docs/lychee.toml +63 -0
  44. data/lib/prosereflect/attribute/base.rb +4 -6
  45. data/lib/prosereflect/attribute/bold.rb +2 -4
  46. data/lib/prosereflect/attribute/href.rb +1 -3
  47. data/lib/prosereflect/attribute/id.rb +7 -7
  48. data/lib/prosereflect/attribute.rb +4 -7
  49. data/lib/prosereflect/blockquote.rb +19 -11
  50. data/lib/prosereflect/bullet_list.rb +36 -29
  51. data/lib/prosereflect/code_block.rb +23 -27
  52. data/lib/prosereflect/code_block_wrapper.rb +12 -13
  53. data/lib/prosereflect/document.rb +14 -22
  54. data/lib/prosereflect/fragment.rb +249 -0
  55. data/lib/prosereflect/hard_break.rb +6 -6
  56. data/lib/prosereflect/heading.rb +14 -15
  57. data/lib/prosereflect/horizontal_rule.rb +23 -14
  58. data/lib/prosereflect/image.rb +32 -23
  59. data/lib/prosereflect/input/html.rb +179 -104
  60. data/lib/prosereflect/input.rb +7 -0
  61. data/lib/prosereflect/list_item.rb +11 -12
  62. data/lib/prosereflect/mark/base.rb +9 -11
  63. data/lib/prosereflect/mark/bold.rb +1 -3
  64. data/lib/prosereflect/mark/code.rb +1 -3
  65. data/lib/prosereflect/mark/italic.rb +1 -3
  66. data/lib/prosereflect/mark/link.rb +1 -3
  67. data/lib/prosereflect/mark/strike.rb +1 -3
  68. data/lib/prosereflect/mark/subscript.rb +1 -3
  69. data/lib/prosereflect/mark/superscript.rb +1 -3
  70. data/lib/prosereflect/mark/underline.rb +1 -3
  71. data/lib/prosereflect/mark.rb +9 -5
  72. data/lib/prosereflect/node.rb +171 -33
  73. data/lib/prosereflect/ordered_list.rb +17 -14
  74. data/lib/prosereflect/output/html.rb +279 -50
  75. data/lib/prosereflect/output.rb +7 -0
  76. data/lib/prosereflect/paragraph.rb +11 -13
  77. data/lib/prosereflect/parser.rb +56 -66
  78. data/lib/prosereflect/resolved_pos.rb +256 -0
  79. data/lib/prosereflect/schema/attribute.rb +57 -0
  80. data/lib/prosereflect/schema/content_match.rb +656 -0
  81. data/lib/prosereflect/schema/fragment.rb +166 -0
  82. data/lib/prosereflect/schema/mark.rb +121 -0
  83. data/lib/prosereflect/schema/mark_type.rb +130 -0
  84. data/lib/prosereflect/schema/node.rb +236 -0
  85. data/lib/prosereflect/schema/node_type.rb +274 -0
  86. data/lib/prosereflect/schema/schema_main.rb +190 -0
  87. data/lib/prosereflect/schema/spec.rb +92 -0
  88. data/lib/prosereflect/schema.rb +39 -0
  89. data/lib/prosereflect/table.rb +12 -13
  90. data/lib/prosereflect/table_cell.rb +13 -13
  91. data/lib/prosereflect/table_header.rb +17 -17
  92. data/lib/prosereflect/table_row.rb +12 -12
  93. data/lib/prosereflect/text.rb +35 -11
  94. data/lib/prosereflect/transform/attr_step.rb +157 -0
  95. data/lib/prosereflect/transform/insert_step.rb +115 -0
  96. data/lib/prosereflect/transform/mapping.rb +82 -0
  97. data/lib/prosereflect/transform/mark_step.rb +269 -0
  98. data/lib/prosereflect/transform/replace_around_step.rb +181 -0
  99. data/lib/prosereflect/transform/replace_step.rb +157 -0
  100. data/lib/prosereflect/transform/slice.rb +91 -0
  101. data/lib/prosereflect/transform/step.rb +89 -0
  102. data/lib/prosereflect/transform/step_map.rb +126 -0
  103. data/lib/prosereflect/transform/structure.rb +120 -0
  104. data/lib/prosereflect/transform/transform.rb +341 -0
  105. data/lib/prosereflect/transform.rb +26 -0
  106. data/lib/prosereflect/user.rb +15 -15
  107. data/lib/prosereflect/version.rb +1 -1
  108. data/lib/prosereflect.rb +30 -17
  109. data/prosereflect.gemspec +17 -16
  110. data/spec/fixtures/documents/formatted_text.yaml +14 -0
  111. data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
  112. data/spec/fixtures/documents/lists_doc.yaml +32 -0
  113. data/spec/fixtures/documents/mixed_content.yaml +40 -0
  114. data/spec/fixtures/documents/nested_doc.yaml +20 -0
  115. data/spec/fixtures/documents/simple_doc.yaml +6 -0
  116. data/spec/fixtures/documents/table_doc.yaml +32 -0
  117. data/spec/fixtures/documents/transform_test.yaml +14 -0
  118. data/spec/fixtures/schema/custom_schema.rb +37 -0
  119. data/spec/fixtures/schema/test_schema.rb +46 -0
  120. data/spec/fixtures/test_builder/helpers.rb +212 -0
  121. data/spec/prosereflect/document_spec.rb +332 -330
  122. data/spec/prosereflect/fragment_spec.rb +273 -0
  123. data/spec/prosereflect/hard_break_spec.rb +125 -125
  124. data/spec/prosereflect/input/html_spec.rb +718 -522
  125. data/spec/prosereflect/node_spec.rb +311 -182
  126. data/spec/prosereflect/output/html_spec.rb +105 -105
  127. data/spec/prosereflect/output/whitespace_spec.rb +248 -0
  128. data/spec/prosereflect/paragraph_spec.rb +275 -274
  129. data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
  130. data/spec/prosereflect/parser_spec.rb +185 -180
  131. data/spec/prosereflect/resolved_pos_spec.rb +74 -0
  132. data/spec/prosereflect/schema/conftest.rb +68 -0
  133. data/spec/prosereflect/schema/content_match_spec.rb +237 -0
  134. data/spec/prosereflect/schema/mark_spec.rb +274 -0
  135. data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
  136. data/spec/prosereflect/schema/node_type_spec.rb +142 -0
  137. data/spec/prosereflect/schema/schema_spec.rb +194 -0
  138. data/spec/prosereflect/table_cell_spec.rb +183 -183
  139. data/spec/prosereflect/table_row_spec.rb +149 -149
  140. data/spec/prosereflect/table_spec.rb +320 -318
  141. data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
  142. data/spec/prosereflect/text_spec.rb +133 -132
  143. data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
  144. data/spec/prosereflect/transform/mapping_spec.rb +226 -0
  145. data/spec/prosereflect/transform/replace_spec.rb +832 -0
  146. data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
  147. data/spec/prosereflect/transform/slice_spec.rb +48 -0
  148. data/spec/prosereflect/transform/step_map_spec.rb +70 -0
  149. data/spec/prosereflect/transform/step_spec.rb +211 -0
  150. data/spec/prosereflect/transform/structure_spec.rb +98 -0
  151. data/spec/prosereflect/transform/transform_spec.rb +238 -0
  152. data/spec/prosereflect/user_spec.rb +31 -28
  153. data/spec/prosereflect_spec.rb +28 -26
  154. data/spec/spec_helper.rb +7 -6
  155. data/spec/support/matchers.rb +6 -6
  156. data/spec/support/shared_examples.rb +49 -49
  157. metadata +96 -5
  158. data/spec/prosereflect/version_spec.rb +0 -11
@@ -0,0 +1,14 @@
1
+ ---
2
+ layout: default
3
+ title: Understanding
4
+ nav_order: 4
5
+ has_children: true
6
+ ---
7
+ = Understanding
8
+
9
+ This section explains the core concepts behind prosereflect's document model.
10
+
11
+ * x:document-model[Document Model] -- Node hierarchy and content model
12
+ * x:fragment[Fragment] -- Flat content collections
13
+ * x:resolved-position[Resolved Positions] -- Position resolution in the document tree
14
+ * x:slice[Slice] -- Document slices with open boundaries
@@ -0,0 +1,53 @@
1
+ ---
2
+ layout: default
3
+ title: Resolved Positions
4
+ parent: Understanding
5
+ nav_order: 3
6
+ ---
7
+ = Resolved Positions
8
+
9
+ `Prosereflect::ResolvedPos` represents a document position that has been resolved to a specific location in the document tree. Resolving a position gives you information about the surrounding context.
10
+
11
+ == Creating a ResolvedPos
12
+
13
+ [source,ruby]
14
+ ----
15
+ doc = Prosereflect::Parser.parse_document(data)
16
+ resolved = doc.resolve(3)
17
+ ----
18
+
19
+ == Properties
20
+
21
+ * `pos` -- the absolute document position
22
+ * `depth` -- how deep in the tree this position is (0 = root)
23
+ * `parent` -- the parent node at current depth
24
+ * `parent_offset` -- offset within the parent node (`pos - start`)
25
+
26
+ == Depth-based Access
27
+
28
+ These methods accept an optional `depth` parameter (default: current depth):
29
+
30
+ * `node(depth)` -- node at the given depth
31
+ * `index(depth)` -- index within parent at depth
32
+ * `start(depth)` -- start position of node at depth
33
+ * `end_(depth)` -- end position of node at depth
34
+
35
+ == Boundary Checks
36
+
37
+ * `block?` -- whether the parent is a block node
38
+ * `inline?` -- whether the parent is an inline node
39
+ * `text_block?` -- whether the parent is a text block
40
+ * `start_of_parent?` -- whether at start of parent
41
+ * `end_of_parent?` -- whether at end of parent
42
+ * `before?` -- whether at the start of the current node
43
+ * `after?` -- whether at the end of the current node
44
+
45
+ == Comparing Positions
46
+
47
+ * `shared_depth(other)` -- the deepest depth shared with another position
48
+ * `block_range(other)` -- create a NodeRange between two positions
49
+
50
+ == Marks
51
+
52
+ * `marks` -- marks at this position
53
+ * `marks_between(from, to, marks)` -- collect marks between positions
@@ -0,0 +1,54 @@
1
+ ---
2
+ layout: default
3
+ title: Slice
4
+ parent: Understanding
5
+ nav_order: 4
6
+ ---
7
+ = Slice
8
+
9
+ `Prosereflect::Transform::Slice` represents a contiguous portion of a document that can be inserted, deleted, or moved. Slices track "open" boundaries for proper node joining during replacements.
10
+
11
+ == Creating Slices
12
+
13
+ [source,ruby]
14
+ ----
15
+ # From a fragment
16
+ slice = Prosereflect::Transform::Slice.new(fragment)
17
+
18
+ # With open boundaries
19
+ slice = Prosereflect::Transform::Slice.new(fragment, open_start: 1, open_end: 1)
20
+
21
+ # Empty slice
22
+ slice = Prosereflect::Transform::Slice.empty
23
+ ----
24
+
25
+ == Open Boundaries
26
+
27
+ `open_start` and `open_end` indicate how many levels of the slice's first/last nodes are "open" (not included in the content). This is used when splitting or joining nodes during transforms:
28
+
29
+ * `open_start = 0` -- the first node is complete
30
+ * `open_start = 1` -- the first node is open at the top (its content extends beyond the slice)
31
+
32
+ == Properties
33
+
34
+ * `content` -- the `Fragment` containing the slice's nodes
35
+ * `open_start` -- number of open levels at the start
36
+ * `open_end` -- number of open levels at the end
37
+ * `size` -- total size including open boundaries (`content_size + open_start + open_end`)
38
+ * `content_size` -- size of just the content fragment
39
+ * `empty?` -- whether the slice has no content and no open boundaries
40
+
41
+ == Methods
42
+
43
+ * `cut(from, to)` -- return a sub-slice
44
+ * `eq?(other)` -- structural equality check
45
+
46
+ == Use in Transforms
47
+
48
+ Slices are used as the replacement content in `ReplaceStep` and `ReplaceAroundStep`:
49
+
50
+ [source,ruby]
51
+ ----
52
+ tx = Prosereflect::Transform::Transform.new(doc)
53
+ tx.replace(2, 5, Prosereflect::Transform::Slice.new(fragment))
54
+ ----
data/docs/lychee.toml ADDED
@@ -0,0 +1,63 @@
1
+ # Lychee Link Checker Configuration
2
+ # For Prosereflect Documentation
3
+ # https://github.com/lycheeverse/lychee
4
+
5
+ # Cache results to avoid re-checking same URLs
6
+ cache = true
7
+ max_cache_age = "1d"
8
+
9
+ # Check both source files and built site
10
+ include_verbatim = true
11
+
12
+ # File types to check (regex patterns)
13
+ include = [
14
+ "_site/**/*.html",
15
+ ".*\\.adoc$",
16
+ ".*\\.md$"
17
+ ]
18
+
19
+ # Excluded paths
20
+ exclude = [
21
+ ".git",
22
+ ".github",
23
+ "node_modules",
24
+ "vendor",
25
+ ".bundle",
26
+ ".sass-cache",
27
+ ".jekyll-cache",
28
+ "_site/.jekyll-cache",
29
+ "Gemfile.lock"
30
+ ]
31
+
32
+ # Link checking behavior
33
+ max_redirects = 10
34
+ max_retries = 3
35
+ timeout = 30
36
+
37
+ # Accept status codes
38
+ accept = [
39
+ "100..=103", # Informational
40
+ "200..=299", # Success
41
+ "429" # Too Many Requests (retry handled by max_retries)
42
+ ]
43
+
44
+ # User agent to identify ourselves
45
+ user_agent = "lychee/prosereflect-docs-link-checker"
46
+
47
+ # Check HTTP, HTTPS, and file:// schemes
48
+ scheme = ["https", "http", "file"]
49
+
50
+ # Handle different link types
51
+ include_mail = false # Don't check mailto: links
52
+
53
+ # Maximum concurrent requests
54
+ max_concurrency = 10
55
+
56
+ # Verbose output for debugging
57
+ verbose = "warn"
58
+
59
+ # Require HTTPS where possible
60
+ require_https = false # Don't enforce
61
+
62
+ # Index files for directory URLs
63
+ index_files = ["index.html"]
@@ -1,24 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../attribute'
4
-
5
3
  module Prosereflect
6
4
  module Attribute
7
5
  class Base < Lutaml::Model::Serializable
8
- PM_TYPE = 'attribute'
6
+ PM_TYPE = "attribute"
9
7
 
10
8
  attribute :type, :string, default: lambda {
11
9
  begin
12
10
  self.class.const_get(:PM_TYPE)
13
11
  rescue StandardError
14
- 'attribute'
12
+ "attribute"
15
13
  end
16
14
  }
17
15
  attribute :value, :string
18
16
 
19
17
  key_value do
20
- map 'type', to: :type, render_default: true
21
- map 'value', to: :value
18
+ map "type", to: :type, render_default: true
19
+ map "value", to: :value
22
20
  end
23
21
 
24
22
  def self.create(type, value)
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Prosereflect
6
4
  module Attribute
7
5
  class Bold < Base
8
- PM_TYPE = 'bold'
6
+ PM_TYPE = "bold"
9
7
 
10
8
  def initialize(options = {})
11
9
  super
12
- self.type = 'bold'
10
+ self.type = "bold"
13
11
  end
14
12
 
15
13
  def attrs
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Prosereflect
6
4
  module Attribute
7
5
  class Href < Base
8
- PM_TYPE = 'href'
6
+ PM_TYPE = "href"
9
7
 
10
8
  attribute :type, :string, default: -> { PM_TYPE }
11
9
  attribute :href, :string
@@ -1,23 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module Prosereflect
6
4
  module Attribute
7
5
  class Id < Base
8
- PM_TYPE = 'id'
6
+ PM_TYPE = "id"
9
7
 
10
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
11
11
  attribute :id, :string
12
12
 
13
13
  key_value do
14
- map 'type', to: :type, render_default: true
15
- map 'id', to: :id
14
+ map "type", to: :type, render_default: true
15
+ map "id", to: :id
16
16
  end
17
17
 
18
18
  # Convert to hash for serialization
19
19
  def to_h
20
- { 'id' => id }
20
+ { "id" => id }
21
21
  end
22
22
  end
23
23
  end
@@ -1,13 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'lutaml/model'
4
-
5
3
  module Prosereflect
6
4
  module Attribute
5
+ autoload :Base, "#{__dir__}/attribute/base"
6
+ autoload :Href, "#{__dir__}/attribute/href"
7
+ autoload :Id, "#{__dir__}/attribute/id"
8
+ autoload :Bold, "#{__dir__}/attribute/bold"
7
9
  end
8
10
  end
9
-
10
- require_relative 'attribute/base'
11
- require_relative 'attribute/href'
12
- require_relative 'attribute/id'
13
- require_relative 'attribute/bold'
@@ -1,21 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
- require_relative 'paragraph'
5
-
6
3
  module Prosereflect
7
4
  # It can contain other block-level content like paragraphs, lists, etc.
8
5
  class Blockquote < Node
9
- PM_TYPE = 'blockquote'
6
+ PM_TYPE = "blockquote"
10
7
 
11
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
12
11
  attribute :citation, :string
13
12
  attribute :attrs, :hash
14
13
 
15
14
  key_value do
16
- map 'type', to: :type, render_default: true
17
- map 'content', to: :content
18
- map 'attrs', to: :attrs
15
+ map "type", to: :type, render_default: true
16
+ map "content", to: :content
17
+ map "attrs", to: :attrs
19
18
  end
20
19
 
21
20
  def initialize(attributes = {})
@@ -56,12 +55,12 @@ module Prosereflect
56
55
  # Update citation/source for the blockquote
57
56
  def citation=(source)
58
57
  self.attrs ||= {}
59
- attrs['citation'] = source
58
+ attrs["citation"] = source
60
59
  end
61
60
 
62
61
  # Get citation/source of the blockquote
63
62
  def citation
64
- attrs&.[]('citation')
63
+ attrs&.[]("citation")
65
64
  end
66
65
 
67
66
  # Check if blockquote has a citation
@@ -72,7 +71,16 @@ module Prosereflect
72
71
  # Remove citation
73
72
  def remove_citation
74
73
  self.attrs ||= {}
75
- attrs.delete('citation')
74
+ attrs.delete("citation")
75
+ end
76
+
77
+ def to_h
78
+ hash = super
79
+ if hash["attrs"]&.key?("citation") && hash["attrs"]["citation"].nil?
80
+ hash["attrs"].delete("citation")
81
+ hash.delete("attrs") if hash["attrs"].empty?
82
+ end
83
+ hash
76
84
  end
77
85
 
78
86
  def add_paragraph(text)
@@ -1,41 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
- require_relative 'list_item'
5
-
6
3
  module Prosereflect
7
4
  # BulletList class represents an unordered list in ProseMirror.
8
5
  class BulletList < Node
9
- PM_TYPE = 'bullet_list'
6
+ PM_TYPE = "bullet_list"
10
7
 
11
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
12
11
  attribute :bullet_style, :string
13
12
  attribute :attrs, :hash
14
13
 
15
14
  key_value do
16
- map 'type', to: :type, render_default: true
17
- map 'attrs', to: :attrs
18
- map 'content', to: :content
15
+ map "type", to: :type, render_default: true
16
+ map "attrs", to: :attrs
17
+ map "content", to: :content
19
18
  end
20
19
 
21
20
  def initialize(attributes = {})
22
21
  attributes[:content] ||= []
23
- attributes[:attrs] ||= { 'bullet_style' => nil }
22
+ # Only apply default if attrs key is completely absent
23
+ unless attributes.key?(:attrs) || attributes.key?("attrs")
24
+ attributes[:attrs] = { "bullet_style" => nil }
25
+ end
24
26
  super
25
27
  end
26
28
 
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')
29
+ # Use *args to distinguish between create (no args) and create(nil)
30
+ # create with no args -> defaults applied
31
+ # create(nil) from parser -> no defaults, attrs explicitly nil
32
+ def self.create(*args)
33
+ if args.empty?
34
+ # No attrs provided - let initialize apply defaults
35
+ new(type: PM_TYPE)
36
+ else
37
+ attrs = args[0]
38
+ new({ type: PM_TYPE, attrs: attrs })
39
+ end
39
40
  end
40
41
 
41
42
  def add_item(text)
@@ -67,17 +68,23 @@ module Prosereflect
67
68
 
68
69
  # Get text content with proper formatting
69
70
  def text_content
70
- return '' unless content
71
+ return "" unless content
71
72
 
72
- content.map { |item| item.respond_to?(:text_content) ? item.text_content : '' }.join("\n")
73
+ content.map do |item|
74
+ item.respond_to?(:text_content) ? item.text_content : ""
75
+ end.join("\n")
73
76
  end
74
77
 
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
78
+ def bullet_style=(value)
79
+ @bullet_style = value
80
+ return if value.nil?
81
+
82
+ self.attrs ||= {}
83
+ attrs["bullet_style"] = value
84
+ end
85
+
86
+ def bullet_style
87
+ @bullet_style || attrs&.[]("bullet_style")
81
88
  end
82
89
  end
83
90
  end
@@ -1,27 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
-
5
3
  module Prosereflect
6
4
  # CodeBlock class represents a code block in ProseMirror.
7
5
  class CodeBlock < Node
8
- PM_TYPE = 'code_block'
6
+ PM_TYPE = "code_block"
9
7
 
10
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
11
11
  attribute :language, :string
12
12
  attribute :line_numbers, :boolean
13
13
  attribute :attrs, :hash
14
14
 
15
15
  key_value do
16
- map 'type', to: :type, render_default: true
17
- map 'attrs', to: :attrs
18
- map 'content', to: :content
16
+ map "type", to: :type, render_default: true
17
+ map "attrs", to: :attrs
18
+ map "content", to: :content
19
19
  end
20
20
 
21
21
  def initialize(attributes = {})
22
22
  attributes[:attrs] ||= {
23
- 'content' => nil,
24
- 'language' => nil
23
+ "content" => nil,
24
+ "language" => nil,
25
25
  }
26
26
  super
27
27
  end
@@ -33,38 +33,38 @@ module Prosereflect
33
33
  def language=(value)
34
34
  @language = value
35
35
  self.attrs ||= {}
36
- attrs['language'] = value
36
+ attrs["language"] = value
37
37
  end
38
38
 
39
39
  def language
40
- @language || attrs&.[]('language')
40
+ @language || attrs&.[]("language")
41
41
  end
42
42
 
43
43
  def line_numbers=(value)
44
44
  @line_numbers = value
45
45
  self.attrs ||= {}
46
- attrs['line_numbers'] = value
46
+ attrs["line_numbers"] = value
47
47
  end
48
48
 
49
49
  def line_numbers
50
- @line_numbers || attrs&.[]('line_numbers') || false
50
+ @line_numbers || attrs&.[]("line_numbers") || false
51
51
  end
52
52
 
53
53
  def content=(value)
54
54
  @content = value
55
55
  self.attrs ||= {}
56
- attrs['content'] = value
56
+ attrs["content"] = value
57
57
  end
58
58
 
59
59
  def content
60
- @content || attrs&.[]('content')
60
+ @content || attrs&.[]("content")
61
61
  end
62
62
 
63
63
  attr_reader :highlight_lines_str
64
64
 
65
65
  def highlight_lines=(lines)
66
66
  @highlight_lines_str = if lines.is_a?(Array)
67
- lines.join(',')
67
+ lines.join(",")
68
68
  else
69
69
  lines.to_s
70
70
  end
@@ -73,7 +73,7 @@ module Prosereflect
73
73
  def highlight_lines
74
74
  return [] unless @highlight_lines_str
75
75
 
76
- @highlight_lines_str.split(',').map(&:to_i)
76
+ @highlight_lines_str.split(",").map(&:to_i)
77
77
  end
78
78
 
79
79
  def text_content
@@ -82,12 +82,8 @@ module Prosereflect
82
82
 
83
83
  def to_h
84
84
  hash = super
85
- hash['attrs'] = {
86
- 'content' => content,
87
- 'language' => language
88
- }
89
- hash['attrs']['line_numbers'] = line_numbers if line_numbers
90
- hash.delete('content')
85
+ hash["attrs"] = { "language" => language }
86
+ hash["attrs"]["line_numbers"] = line_numbers if line_numbers
91
87
  hash
92
88
  end
93
89
 
@@ -96,7 +92,7 @@ module Prosereflect
96
92
  {
97
93
  language: language,
98
94
  line_numbers: line_numbers,
99
- highlight_lines: highlight_lines
95
+ highlight_lines: highlight_lines,
100
96
  }.compact
101
97
  end
102
98
 
@@ -118,14 +114,14 @@ module Prosereflect
118
114
  return content if lines.empty?
119
115
 
120
116
  min_indent = lines.reject(&:empty?)
121
- .map { |line| line[/^\s*/].length }
122
- .min || 0
117
+ .map { |line| line[/^\s*/].length }
118
+ .min || 0
123
119
 
124
120
  normalized_lines = lines.map do |line|
125
121
  if line.empty?
126
122
  line
127
123
  else
128
- line[min_indent..] || ''
124
+ line[min_indent..] || ""
129
125
  end
130
126
  end
131
127
 
@@ -1,27 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
- require_relative 'code_block'
5
-
6
3
  module Prosereflect
7
4
  # CodeBlockWrapper class represents a pre tag that wraps code blocks in ProseMirror.
8
5
  class CodeBlockWrapper < Node
9
- PM_TYPE = 'code_block_wrapper'
6
+ PM_TYPE = "code_block_wrapper"
10
7
 
11
- attribute :type, :string, default: -> { send('const_get', 'PM_TYPE') }
8
+ attribute :type, :string, default: -> {
9
+ self.class.send(:const_get, "PM_TYPE")
10
+ }
12
11
  attribute :line_numbers, :boolean
13
12
  attribute :attrs, :hash
14
13
 
15
14
  key_value do
16
- map 'type', to: :type, render_default: true
17
- map 'attrs', to: :attrs
18
- map 'content', to: :content
15
+ map "type", to: :type, render_default: true
16
+ map "attrs", to: :attrs
17
+ map "content", to: :content
19
18
  end
20
19
 
21
20
  def initialize(attributes = {})
22
21
  attributes[:content] ||= []
23
22
  attributes[:attrs] = {
24
- 'line_numbers' => false
23
+ "line_numbers" => false,
25
24
  }
26
25
  super
27
26
  end
@@ -33,11 +32,11 @@ module Prosereflect
33
32
  def line_numbers=(value)
34
33
  @line_numbers = value
35
34
  self.attrs ||= {}
36
- attrs['line_numbers'] = value
35
+ attrs["line_numbers"] = value
37
36
  end
38
37
 
39
38
  def line_numbers
40
- @line_numbers || attrs&.[]('line_numbers') || false
39
+ @line_numbers || attrs&.[]("line_numbers") || false
41
40
  end
42
41
 
43
42
  def add_code_block(code = nil)
@@ -57,8 +56,8 @@ module Prosereflect
57
56
 
58
57
  def to_h
59
58
  hash = super
60
- hash['attrs'] = {
61
- 'line_numbers' => line_numbers
59
+ hash["attrs"] = {
60
+ "line_numbers" => line_numbers,
62
61
  }
63
62
  hash
64
63
  end