instance method visit_h3

Ruby on Rails edge

Since edge Private — implementation detail, not part of the public API

Signature

visit_h3(node, child_values)

No documentation comment.

Parameters

node req
child_values req
Source
# File actiontext/lib/action_text/markdown_conversion.rb, line 202
      def visit_h3(node, child_values) = visit__heading(node, child_values, 3)
      def visit_h4(node, child_values) = visit__heading(node, child_values, 4)
      def visit_h5(node, child_values) = visit__heading(node, child_values, 5)
      def visit_h6(node, child_values) = visit__heading(node, child_values, 6)

      def visit_blockquote(_node, child_values)
        quoted = join_children(child_values).strip.lines.map { |line| "> #{line}" }.join
        "#{quoted}\n\n"
      end

      def visit_ul(node, child_values)
        items = list_item_lines(node, child_values, prefix: "- ")
        "#{items}\n\n"
      end

      def visit_ol(node, child_values)
        items = list_item_lines(node, child_values, prefix: ->(i) { "#{i + 1}. " })
        "#{items}\n\n"
      end

      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

      def flatten_to_inline(text)
        return text unless text.match?(/[\r\n]/)

        text.gsub(/[\r\n]+/, " ")
      end

      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

      def visit_summary(_node, child_values)
        "**#{join_children(child_values)}**\n\n"
      end

      def visit_br(_node, _child_values)
        "\n"
      end

      def visit_hr(_node, _child_values)
        "---\n\n"
      end

      # Attachment markdown is wrapped in <action-text-markdown> by Content#to_markdown so it passes
      # through without text escaping.
      def visit_action_text_markdown(_node, child_values)
        join_children(child_values)
      end

      # Avoid including content from elements that aren't meaningful for markdown output
      def visit__unsupported(_node, _child_values)
        ""
      end
      alias_method :visit_script, :visit__unsupported
      alias_method :visit_style, :visit__unsupported

      # These elements pass through their content (parent handlers use child_values directly)
      def visit__passthrough(_node, child_values)
        join_children(child_values)
      end
      alias_method :visit_li, :visit__passthrough
      alias_method :visit_td, :visit__passthrough
      alias_method :visit_th, :visit__passthrough
      alias_method :visit_thead, :visit__passthrough
      alias_method :visit_tbody, :visit__passthrough

      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

      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

      def format_list_item(lines, bullet)
        first, *rest = lines
        leader = first.match?(LIST_BULLET) ? LIST_INDENT : bullet
        ([ leader + first ] + rest.map { |line| LIST_INDENT + line }).join("\n")
      end

      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|
          # Nested block elements (e.g., lists and blockquotes) need an initial newline injected
          if !result.empty? && !result.end_with?("\n") && part.end_with?("\n\n")
            result << "\n"
          end
          result << part
        end
        result
      end

      def child_values_for_elements(node, child_values)
        node.children.zip(child_values).filter_map do |child, value|
          value if child.element?
        end
      end

      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

      # Make sure `<strong> hello </strong>` becomes ` **hello** ` and not `** hello **`
      # (the latter is not valid markdown).
      def wrap_emphasis(text, marker)
        leading = text[/\A\s*/]
        trailing = text[/\s*\z/]
        inner = text.strip
        "#{leading}#{marker}#{inner}#{marker}#{trailing}"
      end

      def code_fence(content)
        max_run = content.scan(/`{3,}/).map(&:length).max || 0
        "`" * [3, max_run + 1].max
      end

      def inline_code(content)
        max_run = content.scan(/`+/).map(&:length).max || 0
        fence = "`" * [1, max_run + 1].max
        if content.start_with?("`") || content.end_with?("`")
          "#{fence} #{content} #{fence}"
        else
          "#{fence}#{content}#{fence}"
        end
      end

      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

      def significant_whitespace?(node)
        inline_sibling?(node.previous_sibling) &&
          inline_sibling?(node.next_sibling)
      end

      def inline_sibling?(sibling)
        sibling&.text? || sibling&.name&.in?(INLINE_ELEMENTS)
      end

      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

      def encode_href(href)
        URI::RFC2396_PARSER.escape(href, ENCODE_HREF_CHARS)
      end

      def skip_markdown_escaping?(node)
        node.parent&.name.in?(SKIP_ESCAPING_PARENTS)
      end

Defined in actiontext/lib/action_text/markdown_conversion.rb line 202 · View on GitHub · Improve this page · Find usages on GitHub

Defined in ActionText::MarkdownConversion

Type at least 2 characters to search.

Use the arrow keys to navigate results, Enter to open one, Escape to close.

Keyboard shortcuts

/
Focus search
⌘K / Ctrl-K
Command palette
?
This help
Esc
Close