prose_mirror_ruby 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.
- checksums.yaml +4 -4
- data/README.md +25 -0
- data/lib/prose_mirror/converter.rb +7 -3
- data/lib/prose_mirror/serializers/markdown_serializer.rb +124 -17
- data/lib/prose_mirror/version.rb +1 -1
- data/lib/prose_mirror.rb +1 -0
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78bc17787531ffe01a656e70876935071859afc98eaf814799c165680f9c689a
|
4
|
+
data.tar.gz: 4d43f410e59748ef8e64335baaa8f328622c17057f7bb03ed41d8806bbe0e23e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d97653a9274c6e59271ac20b59dfd9cafe89d85fd7d80583c2b492bbcc2127ee0232cffba75e2b624f0066f888d361ab01c4b8460f294bfd3891bda7932495bf
|
7
|
+
data.tar.gz: ce4af37ef52f6e5a6504dfbd4c6b0a5d780cef3d773cccb8d86ee7aa03d871ae3b6e42894d981b219902c80b4839c08480cb7a7381a34bedc3aedf4d928e5893
|
data/README.md
CHANGED
@@ -134,6 +134,18 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
134
134
|
|
135
135
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
136
136
|
|
137
|
+
For developers looking to contribute or extend this gem, the test suite includes examples of various complex ProseMirror document structures including:
|
138
|
+
|
139
|
+
- Blockquotes with embedded lists
|
140
|
+
- Linked images
|
141
|
+
- Nested lists (ordered and unordered)
|
142
|
+
- Mixed formatting (bold and italic combined)
|
143
|
+
- Code blocks with language specification
|
144
|
+
- Horizontal rules
|
145
|
+
- Tables (pending implementation)
|
146
|
+
|
147
|
+
These tests serve as documentation for how different structures are (or should be) handled.
|
148
|
+
|
137
149
|
## Contributing
|
138
150
|
|
139
151
|
Bug reports and pull requests are welcome on GitHub at https://github.com/firehydrant/prose_mirror.
|
@@ -141,6 +153,19 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/firehy
|
|
141
153
|
## Known Issues
|
142
154
|
|
143
155
|
- Markdown serialization for lists and code blocks may not produce perfect output in all cases. Contributions to improve this are welcome.
|
156
|
+
- Complex document structures like nested lists, linked images, and tables have varying levels of support:
|
157
|
+
- Basic formatting (bold, italic, links) works well
|
158
|
+
- Blockquotes with lists now have improved formatting
|
159
|
+
- Nested lists have significantly improved indentation and spacing
|
160
|
+
- Complex alternating list types (switching between ordered and bullet lists) now render with better formatting
|
161
|
+
- Tables are not currently supported
|
162
|
+
- Custom mark types (beyond the standard strong, em, link, code) may not be properly rendered
|
163
|
+
|
164
|
+
## Recent Improvements
|
165
|
+
|
166
|
+
- **Better Nested List Handling**: The library now supports improved indentation for nested lists (e.g., ordered lists inside bullet lists or vice versa), making the generated Markdown more readable.
|
167
|
+
- **CamelCase Support**: The library now automatically converts camelCase node types (like "orderedList") to snake_case ("ordered_list"), making it compatible with a wider range of ProseMirror documents and schemas.
|
168
|
+
- **Enhanced List Rendering**: Completely refactored list rendering with better indentation, spacing, and handling of complex nested structures.
|
144
169
|
|
145
170
|
## License
|
146
171
|
|
@@ -15,7 +15,8 @@ module ProseMirror
|
|
15
15
|
# @param node_data [Hash] The node data from JSON
|
16
16
|
# @return [Node] The parsed node
|
17
17
|
def self.parse_node(node_data)
|
18
|
-
|
18
|
+
# Convert camelCase node types to snake_case (e.g., "orderedList" -> "ordered_list")
|
19
|
+
type = node_data["type"].underscore
|
19
20
|
attrs = parse_attrs(node_data["attrs"] || {})
|
20
21
|
marks = parse_marks(node_data["marks"] || [])
|
21
22
|
|
@@ -35,7 +36,8 @@ module ProseMirror
|
|
35
36
|
def self.parse_attrs(attrs_data)
|
36
37
|
result = {}
|
37
38
|
attrs_data.each do |key, value|
|
38
|
-
|
39
|
+
# Convert camelCase attr keys to snake_case as well
|
40
|
+
result[key.underscore.to_sym] = value
|
39
41
|
end
|
40
42
|
result
|
41
43
|
end
|
@@ -45,7 +47,9 @@ module ProseMirror
|
|
45
47
|
# @return [Array<Mark>] Array of Mark objects
|
46
48
|
def self.parse_marks(marks_data)
|
47
49
|
marks_data.map do |mark_data|
|
48
|
-
|
50
|
+
# Convert camelCase mark types to snake_case
|
51
|
+
mark_type = mark_data["type"].underscore
|
52
|
+
Mark.new(mark_type, parse_attrs(mark_data["attrs"] || {}))
|
49
53
|
end
|
50
54
|
end
|
51
55
|
end
|
@@ -10,7 +10,15 @@ module ProseMirror
|
|
10
10
|
# Default serializers for various node types
|
11
11
|
DEFAULT_NODE_SERIALIZERS = {
|
12
12
|
blockquote: ->(state, node, parent = nil, index = nil) {
|
13
|
+
# Track that we're in a blockquote to handle lists within blockquotes properly
|
14
|
+
old_in_blockquote = state.instance_variable_get(:@in_blockquote) || false
|
15
|
+
state.instance_variable_set(:@in_blockquote, true)
|
16
|
+
|
17
|
+
# Use the standard blockquote prefix for all content
|
13
18
|
state.wrap_block("> ", nil, node) { state.render_content(node) }
|
19
|
+
|
20
|
+
# Restore the blockquote state
|
21
|
+
state.instance_variable_set(:@in_blockquote, old_in_blockquote)
|
14
22
|
},
|
15
23
|
|
16
24
|
code_block: ->(state, node, parent = nil, index = nil) {
|
@@ -38,27 +46,86 @@ module ProseMirror
|
|
38
46
|
},
|
39
47
|
|
40
48
|
bullet_list: ->(state, node, parent = nil, index = nil) {
|
41
|
-
|
49
|
+
# Special handling for lists inside blockquotes
|
50
|
+
if state.instance_variable_get(:@in_blockquote)
|
51
|
+
# For lists in blockquotes, add the blockquote prefix to each line
|
52
|
+
# Each list item should start with "> * "
|
53
|
+
state.render_list(node, "", ->(_) { "* " }, true)
|
54
|
+
else
|
55
|
+
# Standard list rendering
|
56
|
+
state.render_list(node, " ", ->(_) { "* " })
|
57
|
+
end
|
42
58
|
},
|
43
59
|
|
44
60
|
ordered_list: ->(state, node, parent = nil, index = nil) {
|
45
61
|
start = node.attrs[:order] || 1
|
46
|
-
max_w = (start + node.child_count - 1).to_s.length
|
47
|
-
space = state.repeat(" ", max_w + 2)
|
48
62
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
63
|
+
# Special handling for ordered lists inside blockquotes
|
64
|
+
if state.instance_variable_get(:@in_blockquote)
|
65
|
+
# For lists in blockquotes, add the blockquote prefix to each line
|
66
|
+
# Each list item should start with "> 1. " etc.
|
67
|
+
state.render_list(node, "", ->(i) {
|
68
|
+
"#{start + i}. "
|
69
|
+
}, true)
|
70
|
+
else
|
71
|
+
# Standard ordered list rendering
|
72
|
+
state.render_list(node, " ", ->(i) {
|
73
|
+
"#{start + i}. "
|
74
|
+
})
|
75
|
+
end
|
53
76
|
},
|
54
77
|
|
55
78
|
list_item: ->(state, node, parent = nil, index = nil) {
|
56
|
-
|
79
|
+
# Track that we're processing a list item to handle nested lists
|
80
|
+
old_in_list_item = state.instance_variable_get(:@in_list_item) || false
|
81
|
+
state.instance_variable_set(:@in_list_item, true)
|
82
|
+
|
83
|
+
# Special handling for list items in blockquotes
|
84
|
+
if state.instance_variable_get(:@in_blockquote)
|
85
|
+
# Process the list item content with special handling
|
86
|
+
node.each_with_index do |child, i|
|
87
|
+
if child.type.name == "paragraph"
|
88
|
+
# Render paragraph content directly
|
89
|
+
state.render_inline(child)
|
90
|
+
else
|
91
|
+
# Render other content normally
|
92
|
+
state.render(child, node, i)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
else
|
96
|
+
# Process the list item content normally
|
97
|
+
state.render_content(node)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Restore the previous state
|
101
|
+
state.instance_variable_set(:@in_list_item, old_in_list_item)
|
57
102
|
},
|
58
103
|
|
59
104
|
paragraph: ->(state, node, parent = nil, index = nil) {
|
60
|
-
|
61
|
-
state.
|
105
|
+
# Special handling for paragraphs inside list items to avoid extra whitespace
|
106
|
+
if state.instance_variable_get(:@in_list_item)
|
107
|
+
# Create a clean paragraph renderer for list items
|
108
|
+
old_at_block_start = state.instance_variable_get(:@at_block_start)
|
109
|
+
|
110
|
+
# Render the paragraph content directly with minimal whitespace
|
111
|
+
state.render_inline(node)
|
112
|
+
|
113
|
+
# Restore state
|
114
|
+
state.instance_variable_set(:@at_block_start, old_at_block_start)
|
115
|
+
elsif state.instance_variable_get(:@in_blockquote) && parent&.type&.name == "bullet_list"
|
116
|
+
# Special handling for paragraphs in bullet lists inside blockquotes
|
117
|
+
old_at_block_start = state.instance_variable_get(:@at_block_start)
|
118
|
+
|
119
|
+
# Render with blockquote prefix
|
120
|
+
state.render_inline(node)
|
121
|
+
|
122
|
+
# Restore state
|
123
|
+
state.instance_variable_set(:@at_block_start, old_at_block_start)
|
124
|
+
else
|
125
|
+
# Normal paragraph rendering for non-list items
|
126
|
+
state.render_inline(node)
|
127
|
+
state.close_block(node)
|
128
|
+
end
|
62
129
|
},
|
63
130
|
|
64
131
|
image: ->(state, node, parent = nil, index = nil) {
|
@@ -174,7 +241,7 @@ module ProseMirror
|
|
174
241
|
|
175
242
|
# This class is used to track state and expose methods related to markdown serialization
|
176
243
|
class MarkdownSerializerState
|
177
|
-
attr_accessor :delim, :out, :closed, :in_autolink, :at_block_start, :in_tight_list
|
244
|
+
attr_accessor :delim, :out, :closed, :in_autolink, :at_block_start, :in_tight_list, :in_blockquote
|
178
245
|
attr_reader :nodes, :marks, :options
|
179
246
|
|
180
247
|
# Initialize a new state object
|
@@ -188,6 +255,7 @@ module ProseMirror
|
|
188
255
|
@in_autolink = nil
|
189
256
|
@at_block_start = false
|
190
257
|
@in_tight_list = false
|
258
|
+
@in_blockquote = false
|
191
259
|
|
192
260
|
@options[:tight_lists] = false if @options[:tight_lists].nil?
|
193
261
|
@options[:hard_break_node_name] = "hard_break" if @options[:hard_break_node_name].nil?
|
@@ -428,23 +496,62 @@ module ProseMirror
|
|
428
496
|
end
|
429
497
|
|
430
498
|
# Render a list
|
431
|
-
def render_list(node,
|
499
|
+
def render_list(node, indent, marker_gen, in_blockquote = false)
|
500
|
+
# Clear any closed block
|
432
501
|
if @closed && @closed.type == node.type
|
433
502
|
@closed = nil
|
434
503
|
else
|
435
504
|
ensure_new_line
|
436
505
|
end
|
437
506
|
|
438
|
-
|
507
|
+
# Remember current indentation level
|
508
|
+
old_indent = @delim
|
509
|
+
is_nested = !old_indent.empty?
|
510
|
+
|
511
|
+
# Set up tight list handling
|
512
|
+
old_tight = @in_tight_list
|
513
|
+
@in_tight_list = node.attrs[:tight]
|
439
514
|
|
515
|
+
# Process each list item
|
440
516
|
node.each_with_index do |child, i|
|
441
|
-
|
442
|
-
@
|
443
|
-
|
517
|
+
# For blockquote lists, add the blockquote prefix
|
518
|
+
@delim = if in_blockquote
|
519
|
+
"> "
|
520
|
+
# For nested lists, add standard indentation
|
521
|
+
elsif is_nested
|
522
|
+
" "
|
523
|
+
else
|
524
|
+
""
|
525
|
+
end
|
526
|
+
|
527
|
+
# Write the marker with proper spacing
|
528
|
+
write(marker_gen.call(i))
|
529
|
+
|
530
|
+
# Store the current position after the marker
|
531
|
+
old_delim = @delim
|
532
|
+
|
533
|
+
# Add exactly one space after the marker for content
|
534
|
+
@delim = old_delim + " "
|
535
|
+
|
536
|
+
# Render the item's content
|
444
537
|
render(child, node, i)
|
445
|
-
|
538
|
+
|
539
|
+
# Restore delimiter for next item
|
540
|
+
@delim = old_delim
|
541
|
+
|
542
|
+
# Add a newline after each list item unless it's the last one
|
543
|
+
unless i == node.child_count - 1
|
544
|
+
ensure_new_line
|
545
|
+
end
|
446
546
|
end
|
447
547
|
|
548
|
+
# Restore the tight list setting
|
549
|
+
@in_tight_list = old_tight
|
550
|
+
|
551
|
+
# Restore the original indentation
|
552
|
+
@delim = old_indent
|
553
|
+
|
554
|
+
# Handle spacing between list items and the next content
|
448
555
|
size = (@closed && @closed.type.name == "paragraph" && !node.attrs[:tight]) ? 2 : 1
|
449
556
|
flush_close(size)
|
450
557
|
end
|
data/lib/prose_mirror/version.rb
CHANGED
data/lib/prose_mirror.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prose_mirror_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Ross
|
@@ -10,6 +10,20 @@ bindir: bin
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2025-03-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rake
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|