Class: ProseMirror::Serializers::MarkdownSerializer
- Inherits:
-
Object
- Object
- ProseMirror::Serializers::MarkdownSerializer
- Defined in:
- lib/prose_mirror/serializers/markdown_serializer.rb
Overview
A specification for serializing a ProseMirror document as Markdown/CommonMark text.
Constant Summary collapse
- DEFAULT_NODE_SERIALIZERS =
Default serializers for various node types
{ blockquote: ->(state, node, parent = nil, index = nil) { # Track that we're in a blockquote to handle lists within blockquotes properly old_in_blockquote = state.instance_variable_get(:@in_blockquote) || false state.instance_variable_set(:@in_blockquote, true) # Use the standard blockquote prefix for all content state.wrap_block("> ", nil, node) { state.render_content(node) } # Restore the blockquote state state.instance_variable_set(:@in_blockquote, old_in_blockquote) }, code_block: ->(state, node, parent = nil, index = nil) { # Make sure fence is longer than any dash sequence within content backticks = node.text_content.scan(/`{3,}/m) fence = backticks.empty? ? "```" : (backticks.sort.last + "`") state.write(fence + (node.attrs[:params] || "") + "\n") state.text(node.text_content, false) # Add newline before closing marker state.write("\n") state.write(fence) state.close_block(node) }, heading: ->(state, node, parent = nil, index = nil) { level = node.attrs[:level].to_i.clamp(1, 6) state.write(state.repeat("#", level) + " ") state.render_inline(node, false) state.close_block(node) }, horizontal_rule: ->(state, node, parent = nil, index = nil) { state.write(node.attrs[:markup] || "---") state.close_block(node) }, bullet_list: ->(state, node, parent = nil, index = nil) { # Special handling for lists inside blockquotes if state.instance_variable_get(:@in_blockquote) # For lists in blockquotes, add the blockquote prefix to each line # Each list item should start with "> * " state.render_list(node, "", ->(_) { "* " }, true) else # Standard list rendering state.render_list(node, " ", ->(_) { "* " }) end }, ordered_list: ->(state, node, parent = nil, index = nil) { start = node.attrs[:order] || 1 # Special handling for ordered lists inside blockquotes if state.instance_variable_get(:@in_blockquote) # For lists in blockquotes, add the blockquote prefix to each line # Each list item should start with "> 1. " etc. state.render_list(node, "", ->(i) { "#{start + i}. " }, true) else # Standard ordered list rendering state.render_list(node, " ", ->(i) { "#{start + i}. " }) end }, list_item: ->(state, node, parent = nil, index = nil) { # Track that we're processing a list item to handle nested lists old_in_list_item = state.instance_variable_get(:@in_list_item) || false state.instance_variable_set(:@in_list_item, true) # Special handling for list items in blockquotes if state.instance_variable_get(:@in_blockquote) # Process the list item content with special handling node.each_with_index do |child, i| if child.type.name == "paragraph" # Render paragraph content directly state.render_inline(child) else # Render other content normally state.render(child, node, i) end end else # Process the list item content normally state.render_content(node) end # Restore the previous state state.instance_variable_set(:@in_list_item, old_in_list_item) }, paragraph: ->(state, node, parent = nil, index = nil) { # Special handling for paragraphs inside list items to avoid extra whitespace if state.instance_variable_get(:@in_list_item) # Create a clean paragraph renderer for list items old_at_block_start = state.instance_variable_get(:@at_block_start) # Render the paragraph content directly with minimal whitespace state.render_inline(node) # Restore state state.instance_variable_set(:@at_block_start, old_at_block_start) elsif state.instance_variable_get(:@in_blockquote) && parent&.type&.name == "bullet_list" # Special handling for paragraphs in bullet lists inside blockquotes old_at_block_start = state.instance_variable_get(:@at_block_start) # Render with blockquote prefix state.render_inline(node) # Restore state state.instance_variable_set(:@at_block_start, old_at_block_start) else # Normal paragraph rendering for non-list items state.render_inline(node) state.close_block(node) end }, image: ->(state, node, parent = nil, index = nil) { state.write("![" + state.esc(node.attrs[:alt] || "") + "](" + node.attrs[:src].gsub(/[\(\)]/, "\\\\\\&") + (node.attrs[:title] ? ' "' + node.attrs[:title].gsub('"', '\\"') + '"' : "") + ")") }, hard_break: ->(state, node, parent, index) { (index + 1...parent.child_count).each do |i| if parent.child(i).type != node.type state.write("\\\n") return end end }, text: ->(state, node, parent = nil, index = nil) { state.text(node.text, !state.in_autolink) }, # Table serialization support table: ->(state, node, parent = nil, index = nil) { # Track that we're in a table old_in_table = state.instance_variable_get(:@in_table) || false state.instance_variable_set(:@in_table, true) # Render table content state.render_content(node) # Restore table state state.instance_variable_set(:@in_table, old_in_table) # Only add newline if not at the end of the document if parent && index < parent.child_count - 1 state.close_block(node) end }, table_row: ->(state, node, parent = nil, index = nil) { # Write row separator after headers if index == 1 && parent && parent.child(0).content.any? { |cell| cell.type.name == "table_header" } state.write("|") node.content.each do |_| state.write(" --- |") end state.write("\n") end # Write row content state.write("|") state.render_content(node) # Add newline unless this is the last row if parent && index < parent.child_count - 1 state.write("\n") end }, table_header: ->(state, node, parent = nil, index = nil) { state.write(" ") # Render content with marks node.content.each do |cell_content| state.render_inline(cell_content) end state.write(" |") }, table_cell: ->(state, node, parent = nil, index = nil) { state.write(" ") # Render content with marks node.content.each do |cell_content| state.render_inline(cell_content) end state.write(" |") } }
- DEFAULT_MARK_SERIALIZERS =
Default serializers for various mark types
{ em: { open: "*", close: "*", mixable: true, expel_enclosing_whitespace: true }, italic: { open: "*", close: "*", mixable: true, expel_enclosing_whitespace: true }, text_style: { open: "", close: "", mixable: true, expel_enclosing_whitespace: false }, inline_thread: { open: "", close: "", mixable: true, expel_enclosing_whitespace: false }, strong: { open: "**", close: "**", mixable: true, expel_enclosing_whitespace: true }, link: { open: ->(state, mark, parent, index) { state.in_autolink = ProseMirror::Serializers.is_plain_url(mark, parent, index) state.in_autolink ? "<" : "[" }, close: ->(state, mark, parent, index) { in_autolink = state.in_autolink state.in_autolink = nil if in_autolink ">" else "](" + mark.attrs[:href].gsub(/[\(\)"]/, "\\\\\\&") + (mark.attrs[:title] ? ' "' + mark.attrs[:title].gsub('"', '\\"') + '"' : "") + ")" end }, mixable: true }, code: { open: ->(_, mark, parent, index) { ProseMirror::Serializers.backticks_for(parent.child(index), -1) }, close: ->(_, mark, parent, index) { ProseMirror::Serializers.backticks_for(parent.child(index - 1), 1) }, escape: false }, strikethrough: { open: "~~", close: "~~", mixable: true, expel_enclosing_whitespace: true } }
Instance Attribute Summary collapse
-
#marks ⇒ Object
readonly
Returns the value of attribute marks.
-
#nodes ⇒ Object
readonly
Returns the value of attribute nodes.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Instance Method Summary collapse
-
#initialize(nodes, marks, options = {}) ⇒ MarkdownSerializer
constructor
Constructor with node serializers, mark serializers, and options.
-
#serialize(content, options = {}) ⇒ String
Serialize the content of the given node to CommonMark.
Constructor Details
#initialize(nodes, marks, options = {}) ⇒ MarkdownSerializer
Constructor with node serializers, mark serializers, and options
285 286 287 288 289 |
# File 'lib/prose_mirror/serializers/markdown_serializer.rb', line 285 def initialize(nodes, marks, = {}) @nodes = nodes @marks = marks @options = end |
Instance Attribute Details
#marks ⇒ Object (readonly)
Returns the value of attribute marks.
8 9 10 |
# File 'lib/prose_mirror/serializers/markdown_serializer.rb', line 8 def marks @marks end |
#nodes ⇒ Object (readonly)
Returns the value of attribute nodes.
8 9 10 |
# File 'lib/prose_mirror/serializers/markdown_serializer.rb', line 8 def nodes @nodes end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
8 9 10 |
# File 'lib/prose_mirror/serializers/markdown_serializer.rb', line 8 def @options end |
Instance Method Details
#serialize(content, options = {}) ⇒ String
Serialize the content of the given node to CommonMark
295 296 297 298 299 300 |
# File 'lib/prose_mirror/serializers/markdown_serializer.rb', line 295 def serialize(content, = {}) = @options.merge() state = MarkdownSerializerState.new(@nodes, @marks, ) state.render_content(content) state.out end |