Module: ActionText::MarkdownConversion
| Relationships & Source Files | |
| Defined in: | actiontext/lib/action_text/markdown_conversion.rb |
Overview
Converts an HTML fragment into a Markdown string. Used by Content#to_markdown and Fragment#to_markdown to produce Markdown representations of rich text.
Example: => Release Notes
# Release Notes, a markdown heading.
Note that this converter escapes text nodes so it won't render as markdown.
Example: # Release Notes => # Release Notes, not a heading.
Constant Summary
-
BOLD_TAGS =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 97%w[b strong].freeze
-
ENCODE_HREF_CHARS =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 101/[() <>\n\r\t]/ -
INLINE_ELEMENTS =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 111[ RAW_MARKDOWN_TAG_NAME, *%w[ a abbr b bdi bdo cite code data del dfn em i kbd mark q rp rt ruby s samp small span strong sub sup time u var ] ].freeze
-
ITALIC_TAGS =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 98%w[i em].freeze
-
LEADING_PRETTY_PRINT_WHITESPACE =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 116/\A\s*\n\s*/ -
LIST_BULLET =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 99/\A(-|\d+\.) / -
LIST_INDENT =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 100" " -
MARKDOWN_METACHARACTERS =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 102/ [\\`*_{}\[\]|~<>] # metacharacters that should be escaped generally | \A\#(?=[\s\#]|\z) # leading hash before space or another hash: ATX heading | \A=(?=[=\s]|\z) # leading equals before space or another equals: setext heading | \A- # leading hyphen: list item, thematic break, or setext heading | \A\+(?=\s|\z) # leading plus before space: list item | \A\d+\K\.(?=\s|\z) # leading "1." with trailing space: ordered list item (only the dot is matched) /x -
RAW_MARKDOWN_TAG_NAME =
Internal use only
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 21"action-text-markdown" -
SINGLE_LINE_ANCESTORS =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 115[ *INLINE_ELEMENTS, *%w[h1 h2 h3 h4 h5 h6 summary tr td th] ].freeze
-
SKIP_ESCAPING_PARENTS =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 110[ RAW_MARKDOWN_TAG_NAME, "code", "pre" ].freeze
-
TRAILING_PRETTY_PRINT_WHITESPACE =
private
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 117/\s*\n\s*\z/
Instance Method Summary
-
#escape_markdown_text(text)
Backslash-escapes CommonMark metacharacters in
textso they are treated as literal characters by Markdown renderers. -
#fragment_by_unwrapping_raw_markdown_tags(fragment)
Returns a copy of
fragmentwithelements replaced by their children, leaving the text to be escaped like any other. -
#markdown_link(title, url, image: false)
Returns a Markdown link: title`.
-
#node_to_markdown(node)
Converts a Nokogiri HTML
nodeinto a Markdown string. -
#render_attachment(attachment, attachment_links: false)
Returns an element holding
attachment's Markdown, for Content#to_markdown to substitute in place of the attachment. - #ancestor_named?(node, names, max_depth:) ⇒ Boolean private
- #block_value?(value) ⇒ Boolean private
- #child_values_for_elements(node, child_values) private
- #code_fence(content) private
- #encode_href(href) private
- #flatten_to_inline(text) private
-
#format_list_item(lines, bullet)
private
A list item's later lines have to be indented to the width of its marker.
-
#inline_code(content)
private
Two things break a code span's delimiter.
- #inline_sibling?(sibling) ⇒ Boolean private
- #join_children(child_values) private
- #list_item_lines(list_node, child_values, prefix:) private
- #markdown_for_node(node, child_values) private
-
#normalize_line_endings(text)
private
Markdown ends a line at a bare CR, but
String#linesandString#split("\n") do not, so a CR inside a fence would slip past the indentation #format_list_item and #visit_blockquote add to each line and land outside the block. - #significant_whitespace?(node) ⇒ Boolean private
-
#single_line_context?(node) ⇒ Boolean
private
A fenced code block opens only at the start of a line.
- #skip_markdown_escaping?(node) ⇒ Boolean private
- #stringify(value) private
- #strip_pretty_print_indentation(node) private
-
#visit__container(_node, child_values)
(also: #visit_div, #visit_li, #visit_td, #visit_th, #visit_thead, #visit_tbody)
private
A container contributes no Markdown of its own, and neither does an element with no visitor at all.
- #visit__heading(_node, child_values, level) private
- #visit__table_header_row(node, child_values) private
-
#visit__unsupported(_node, _child_values)
(also: #visit_script, #visit_style)
private
Avoid including content from elements that aren't meaningful for markdown output.
- #visit_a(node, child_values) private
-
#visit_action_text_markdown(_node, child_values)
private
Attachmentmarkdown is wrapped inby Content#to_markdown so it passes through without text escaping. -
#visit_b(node, child_values)
private
Alias for #visit_strong.
- #visit_blockquote(_node, child_values) private
- #visit_br(_node, _child_values) private
- #visit_code(node, child_values) private
-
#visit_del(_node, child_values)
private
Alias for #visit_s.
-
#visit_div(_node, child_values)
private
Alias for #visit__container.
- #visit_em(node, child_values) (also: #visit_i) private
- #visit_h1(node, child_values) private
- #visit_h2(node, child_values) private
- #visit_h3(node, child_values) private
- #visit_h4(node, child_values) private
- #visit_h5(node, child_values) private
- #visit_h6(node, child_values) private
- #visit_hr(_node, _child_values) private
-
#visit_i(node, child_values)
private
Alias for #visit_em.
-
#visit_li(_node, child_values)
private
Alias for #visit__container.
- #visit_ol(node, child_values) private
- #visit_p(_node, child_values) private
- #visit_pre(node, child_values) private
- #visit_s(_node, child_values) (also: #visit_del) private
-
#visit_script(_node, _child_values)
private
Alias for #visit__unsupported.
- #visit_strong(node, child_values) (also: #visit_b) private
-
#visit_style(_node, _child_values)
private
Alias for #visit__unsupported.
- #visit_summary(_node, child_values) private
-
#visit_tbody(_node, child_values)
private
Alias for #visit__container.
-
#visit_td(_node, child_values)
private
Alias for #visit__container.
-
#visit_th(_node, child_values)
private
Alias for #visit__container.
-
#visit_thead(_node, child_values)
private
Alias for #visit__container.
- #visit_tr(node, child_values) private
- #visit_ul(node, child_values) private
-
#wrap_emphasis(text, marker)
private
Make sure
hellobecomeshelloand not** hello **(the latter is not valid markdown).
Instance Method Details
#ancestor_named?(node, names, max_depth:) ⇒ Boolean (private)
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 433
def ancestor_named?(node, names, max_depth:) current = node.parent max_depth.times do break unless current&.element? return true if current.name.in?(names) current = current.parent end false end
#block_value?(value) ⇒ Boolean (private)
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 295
def block_value?(value) stringify(value).end_with?("\n\n") end
#child_values_for_elements(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 366
def child_values_for_elements(node, child_values) node.children.zip(child_values).filter_map do |child, value| value if child.element? end end
#code_fence(content) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 394
def code_fence(content) max_run = content.scan(/`{3,}/).map(&:length).max || 0 "`" * [3, max_run + 1].max end
#encode_href(href) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 443
def encode_href(href) URI::RFC2396_PARSER.escape(href, ENCODE_HREF_CHARS) end
#escape_markdown_text(text)
Backslash-escapes CommonMark metacharacters in text so they are treated
as literal characters by Markdown renderers.
MarkdownConversion.escape_markdown_text("**Important**")
# => "\\*\\*Important\\*\\*"
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 92
def escape_markdown_text(text) text.gsub(MARKDOWN_METACHARACTERS) { |c| "\\#{c}" } end
#flatten_to_inline(text) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 230
def flatten_to_inline(text) return text unless text.match?(/[\r\n]/) text.gsub(/[\r\n]+/, " ") end
#format_list_item(lines, bullet) (private)
A list item's later lines have to be indented to the width of its marker. Indent them
less and the item ends there, which for a fenced code block means the fence closes
early and the rest of the pre content #markdown_for_node emits unescaped is released
as Markdown source. - happens to be as wide as LIST_INDENT; 1. is not.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 329
def format_list_item(lines, bullet) first, *rest = lines leader = first.match?(LIST_BULLET) ? LIST_INDENT : bullet indent = " " * leader.length ([ leader + first ] + rest.map { |line| indent + line }).join("\n") end
#fragment_by_unwrapping_raw_markdown_tags(fragment)
Returns a copy of fragment with elements replaced by their
children, leaving the text to be escaped like any other.
#render_attachment wraps already-rendered Markdown in that element so #node_to_markdown
emits it without escaping. Only Action Text may do that, so Content unwraps
the element while canonicalizing: anything carrying it at that point came from outside
the framework.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 44
def (fragment) ActionText::Fragment.wrap(fragment).update do |source| source.css(RAW_MARKDOWN_TAG_NAME).each do |node| node.replace(node.children) end end end
#inline_code(content) (private)
Two things break a code span's delimiter. A blank line closes the paragraph before the closing backtick string arrives -- Markdown turns the line endings inside a code span into spaces anyway, so collapse them and the span always closes. And a lone backtick followed by whitespace does not open a span in every renderer (kramdown refuses it), so widen the delimiter when the content leads with whitespace.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 404
def inline_code(content) content = flatten_to_inline(content) max_run = content.scan(/`+/).map(&:length).max || 0 fence = "`" * [content.match?(/\A\s/) ? 2 : 1, max_run + 1].max if content.start_with?("`") || content.end_with?("`") "#{fence} #{content} #{fence}" else "#{fence}#{content}#{fence}" end end
#inline_sibling?(sibling) ⇒ Boolean (private)
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 429
def inline_sibling?(sibling) sibling&.text? || sibling&.name&.in?(INLINE_ELEMENTS) end
#join_children(child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 336
def join_children(child_values) merged = [] child_values.each do |value| # Merge adjacent bold/italic runs which Lexxy emits if value.is_a?(Array) && (value[0] == :bold || value[0] == :italic) if merged.last.is_a?(Array) && merged.last[0] == value[0] merged.last[1] = merged.last[1] + value[1] else merged << [ value[0], value[1] ] end else merged << value end end parts = merged.map { |v| stringify(v) } result = +"" parts.each do |part| # A block child has to begin its own block. Renderers disagree about whether a fence or # a list may interrupt a paragraph, and one that says no releases the content the fence # was holding, so separate with a blank line rather than a single newline. if !result.empty? && part.end_with?("\n\n") result << "\n" until result.end_with?("\n\n") end result << part end result end
#list_item_lines(list_node, child_values, prefix:) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 313
def list_item_lines(list_node, child_values, prefix:) element_values = child_values_for_elements(list_node, child_values) element_values.each_with_index.filter_map do |value, index| text = stringify(value) lines = text.split("\n").reject(&:blank?) next if lines.empty? bullet = prefix.respond_to?(:call) ? prefix.call(index) : prefix format_list_item(lines, bullet) end.join("\n") end
#markdown_for_node(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 122
def markdown_for_node(node, child_values) if node.text? if node.content.blank? && !significant_whitespace?(node) "" elsif skip_markdown_escaping?(node) node.content else escape_markdown_text(strip_pretty_print_indentation(node)) end elsif node.element? method_name = :"visit_#{node.name.tr("-", "_")}" if respond_to?(method_name, true) send(method_name, node, child_values) else visit__container(node, child_values) end else join_children(child_values) end end
#markdown_link(title, url, image: false)
Returns a Markdown link: title`.
Escapes metacharacters in title, and percent-encodes characters in url that would break
the link syntax.
MarkdownConversion.markdown_link("photo", "https://example.com/photo_(large).png")
# => <a href='https://example.com/photo_%28large%29.png'>photo</a>"
Pass image: true to produce an image link ().
MarkdownConversion.markdown_link("photo", "https://example.com/photo.png", image: true)
# => ""
If the URI scheme is not allowed (per Rails::HTML::Sanitizer.allowed_uri?), returns the
escaped title wrapped in escaped brackets (\[title\]).
MarkdownConversion.markdown_link("click", "javascript:alert(1)")
# => "\\[click\\]"
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 79
def markdown_link(title, url, image: false) if Rails::HTML::Sanitizer.allowed_uri?(url) "#{"!" if image}[#{escape_markdown_text(title)}](#{encode_href(url)})" else "\\[#{escape_markdown_text(title)}\\]" end end
#node_to_markdown(node)
Converts a Nokogiri HTML node into a Markdown string.
node = Nokogiri::HTML4.fragment("<p>Hello <strong>world</strong></p>")
MarkdownConversion.node_to_markdown(node) # => "Hello **world**"
NOTE: text inside elements is emitted without escaping, so this
method is not safe for untrusted content. Convert user-supplied markup through
Content, which strips those elements while canonicalizing.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 31
def node_to_markdown(node) BottomUpReducer.new(node).reduce do |n, child_values| markdown_for_node(n, child_values) end.strip end
#normalize_line_endings(text) (private)
Markdown ends a line at a bare CR, but String#lines and String#split("\n") do not, so a
CR inside a fence would slip past the indentation #format_list_item and #visit_blockquote
add to each line and land outside the block.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 239
def normalize_line_endings(text) text.gsub(/\r\n?/, "\n") end
#render_attachment(attachment, attachment_links: false)
Returns an element holding attachment's Markdown, for Content#to_markdown to
substitute in place of the attachment. #node_to_markdown emits the element's text verbatim
rather than escaping it as ordinary Markdown source.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 55
def (, attachment_links: false) ActionText::HtmlConversion.create_element(RAW_MARKDOWN_TAG_NAME).tap do |node| node.content = .to_markdown(attachment_links: ) end end
#significant_whitespace?(node) ⇒ Boolean (private)
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 424
def significant_whitespace?(node) inline_sibling?(node.previous_sibling) && inline_sibling?(node.next_sibling) end
#single_line_context?(node) ⇒ Boolean (private)
A fenced code block opens only at the start of a line. A link, a heading, a summary
and a table row or cell each splice their descendants into a line they have already
begun, and Markdown cannot hold a block inside an inline element at all, so under
any of them the fence never opens and the pre content #markdown_for_node emits
unescaped is released as Markdown source. See SKIP_ESCAPING_PARENTS.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 452
def single_line_context?(node) node.ancestors.any? { |ancestor| ancestor.element? && ancestor.name.in?(SINGLE_LINE_ANCESTORS) } end
#skip_markdown_escaping?(node) ⇒ Boolean (private)
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 456
def skip_markdown_escaping?(node) node.parent&.name.in?(SKIP_ESCAPING_PARENTS) end
#stringify(value) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 372
def stringify(value) case value when Array case value[0] when :bold then wrap_emphasis(value[1], "**") when :italic then wrap_emphasis(value[1], "*") else value.join end else value.to_s end end
#strip_pretty_print_indentation(node) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 415
def strip_pretty_print_indentation(node) content = node.content return content unless content.include?("\n") content .sub(LEADING_PRETTY_PRINT_WHITESPACE, inline_sibling?(node.previous_sibling) ? " " : "") .sub(TRAILING_PRETTY_PRINT_WHITESPACE, inline_sibling?(node.next_sibling) ? " " : "") end
#visit__container(_node, child_values) (private) Also known as: #visit_div, #visit_li, #visit_td, #visit_th, #visit_thead, #visit_tbody
A container contributes no Markdown of its own, and neither does an element with no
visitor at all. #join_children reads a trailing blank line as "this value is a block"
and uses it to keep the next value off the same line, so a container holding a block
has to report one too. Flatten that away and a fence inside the container lands
mid-line, releasing the pre content #markdown_for_node emits unescaped.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 276
def visit__container(_node, child_values) inner = join_children(child_values) if child_values.any? { |value| block_value?(value) } "#{inner.rstrip}\n\n" else inner end end
#visit__heading(_node, child_values, level) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 196
def visit__heading(_node, child_values, level) "#{"#" * level} #{join_children(child_values)}\n\n" end
#visit__table_header_row(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 306
def visit__table_header_row(node, child_values) cells = child_values_for_elements(node, child_values).map { |v| stringify(v).strip } row = "| #{cells.join(" | ")} |\n" separator = "| #{Array.new(cells.size, "---").join(" | ")} |\n" "#{row}#{separator}" end
#visit__unsupported(_node, _child_values) (private) Also known as: #visit_script, #visit_style
Avoid including content from elements that aren't meaningful for markdown output
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 300
def visit__unsupported(_node, _child_values) "" end
#visit_a(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 221
def visit_a(node, child_values) inner = join_children(child_values) if (href = node["href"]) && Rails::HTML::Sanitizer.allowed_uri?(href) "[#{flatten_to_inline(inner)}](#{encode_href(href)})" else inner end end
#visit_action_text_markdown(_node, child_values) (private)
Attachment markdown is wrapped in
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 267
def visit_action_text_markdown(_node, child_values) join_children(child_values) end
#visit_b(node, child_values) (private)
Alias for #visit_strong.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 153
alias_method :visit_b, :visit_strong
#visit_blockquote(_node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 206
def visit_blockquote(_node, child_values) quoted = join_children(child_values).strip.lines.map { |line| "> #{line}" }.join "#{quoted}\n\n" end
#visit_br(_node, _child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 257
def visit_br(_node, _child_values) "\n" end
#visit_code(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 172
def visit_code(node, child_values) inner = join_children(child_values) if node.parent&.name == "pre" inner else inline_code(inner) end end
#visit_del(_node, child_values) (private)
Alias for #visit_s.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 170
alias_method :visit_del, :visit_s
#visit_div(_node, child_values) (private)
Alias for #visit__container.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 288
alias_method :visit_div, :visit__container
#visit_em(node, child_values) (private) Also known as: #visit_i
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 155
def visit_em(node, child_values) inner = join_children(child_values) # lexxy redundantly wraps emphasized subtrees in `<i>` if ancestor_named?(node, ITALIC_TAGS, max_depth: 4) inner else [ :italic, inner ] end end
#visit_h1(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 199
def visit_h1(node, child_values) = visit__heading(node, child_values, 1)
#visit_h2(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 200
def visit_h2(node, child_values) = visit__heading(node, child_values, 2)
#visit_h3(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 201
def visit_h3(node, child_values) = visit__heading(node, child_values, 3)
#visit_h4(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 202
def visit_h4(node, child_values) = visit__heading(node, child_values, 4)
#visit_h5(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 203
def visit_h5(node, child_values) = visit__heading(node, child_values, 5)
#visit_h6(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 204
def visit_h6(node, child_values) = visit__heading(node, child_values, 6)
#visit_hr(_node, _child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 261
def visit_hr(_node, _child_values) "---\n\n" end
#visit_i(node, child_values) (private)
Alias for #visit_em.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 165
alias_method :visit_i, :visit_em
#visit_li(_node, child_values) (private)
Alias for #visit__container.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 289
alias_method :visit_li, :visit__container
#visit_ol(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 216
def visit_ol(node, child_values) items = list_item_lines(node, child_values, prefix: ->(i) { "#{i + 1}. " }) "#{items}\n\n" end
#visit_p(_node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 192
def visit_p(_node, child_values) "#{join_children(child_values)}\n\n" end
#visit_pre(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 181
def visit_pre(node, child_values) inner = normalize_line_endings(join_children(child_values)).delete_prefix("\n").delete_suffix("\n") if single_line_context?(node) inline_code(inner) else fence = code_fence(inner) "#{fence}\n#{inner}\n#{fence}\n\n" end end
#visit_s(_node, child_values) (private) Also known as: #visit_del
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 167
def visit_s(_node, child_values) "~~#{join_children(child_values)}~~" end
#visit_script(_node, _child_values) (private)
Alias for #visit__unsupported.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 303
alias_method :visit_script, :visit__unsupported
#visit_strong(node, child_values) (private) Also known as: #visit_b
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 143
def visit_strong(node, child_values) inner = join_children(child_values) # lexxy redundantly wraps bold subtrees in `<b>` if ancestor_named?(node, BOLD_TAGS, max_depth: 4) inner else [ :bold, inner ] end end
#visit_style(_node, _child_values) (private)
Alias for #visit__unsupported.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 304
alias_method :visit_style, :visit__unsupported
#visit_summary(_node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 253
def visit_summary(_node, child_values) "**#{join_children(child_values)}**\n\n" end
#visit_tbody(_node, child_values) (private)
Alias for #visit__container.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 293
alias_method :visit_tbody, :visit__container
#visit_td(_node, child_values) (private)
Alias for #visit__container.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 290
alias_method :visit_td, :visit__container
#visit_th(_node, child_values) (private)
Alias for #visit__container.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 291
alias_method :visit_th, :visit__container
#visit_thead(_node, child_values) (private)
Alias for #visit__container.
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 292
alias_method :visit_thead, :visit__container
#visit_tr(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 243
def visit_tr(node, child_values) # lexxy does not emit `thead`, so we need to infer header rows from `tr` contents if node.element_children.all? { |cell| cell.name == "th" } visit__table_header_row(node, child_values) else cells = child_values_for_elements(node, child_values).map { |v| stringify(v).strip } "| #{cells.join(" | ")} |\n" end end
#visit_ul(node, child_values) (private)
[ GitHub ]# File 'actiontext/lib/action_text/markdown_conversion.rb', line 211
def visit_ul(node, child_values) items = list_item_lines(node, child_values, prefix: "- ") "#{items}\n\n" end
#wrap_emphasis(text, marker) (private)
Make sure hello becomes hello and not ** hello **
(the latter is not valid markdown).
# File 'actiontext/lib/action_text/markdown_conversion.rb', line 387
def wrap_emphasis(text, marker) leading = text[/\A\s*/] trailing = text[/\s*\z/] inner = text.strip "#{leading}#{marker}#{inner}#{marker}#{trailing}" end