summaryrefslogtreecommitdiff
path: root/lib/banzai/filter/table_of_contents_tag_filter.rb
blob: 4e80b543e2d08272185d2fe58ea83ddd75a55940 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# frozen_string_literal: true

module Banzai
  module Filter
    # Using `[[_TOC_]]` or `[TOC]` (both case insensitive), inserts a Table of Contents list.
    #
    # `[[_TOC_]]` is based on the Gollum syntax. This way we have
    # some consistency between with wiki and normal markdown.
    # The support for this has been removed from GollumTagsFilter
    #
    # `[toc]` is a generally accepted form, used by Typora for example.
    #
    # Based on Banzai::Filter::GollumTagsFilter
    class TableOfContentsTagFilter < HTML::Pipeline::Filter
      TEXT_QUERY = %q(descendant-or-self::text()[ancestor::p and contains(translate(., 'TOC', 'toc'), 'toc')])

      def call
        return doc if context[:no_header_anchors]

        doc.xpath(TEXT_QUERY).each do |node|
          if toc_tag?(node)
            # Support [TOC] / [toc] tags, which don't have a wrapping <em>-tag
            process_toc_tag(node)
          elsif toc_tag_em?(node)
            # Support Gollum like ToC tag (`[[_TOC_]]` / `[[_toc_]]`), which will be converted
            # into `[[<em>TOC</em>]]` by the markdown filter, so it
            # needs special-case handling
            process_toc_tag_em(node)
          end
        end

        doc
      end

      private

      # Replace an entire `[[<em>TOC</em>]]` node with the result generated by
      # TableOfContentsFilter
      def process_toc_tag_em(node)
        process_toc_tag(node.parent)
      end

      # Replace an entire `[TOC]` node with the result generated by
      # TableOfContentsFilter
      def process_toc_tag(node)
        # we still need to go one step up to also replace the surrounding <p></p>
        node.parent.replace(result[:toc].presence || '')
      end

      def toc_tag_em?(node)
        node.content.casecmp?('toc') &&
          node.parent.name == 'em' &&
          node.parent.parent.text.casecmp?('[[toc]]')
      end

      def toc_tag?(node)
        node.parent.text.casecmp?('[toc]')
      end
    end
  end
end