]> cat aescling's git repositories - mastodon.git/blob - app/lib/toc_generator.rb
Respect original ID with ToC (#11895)
[mastodon.git] / app / lib / toc_generator.rb
1 # frozen_string_literal: true
2
3 class TOCGenerator
4 TARGET_ELEMENTS = %w(h1 h2 h3 h4 h5 h6).freeze
5 LISTED_ELEMENTS = %w(h2 h3).freeze
6
7 class Section
8 attr_accessor :depth, :title, :children, :anchor
9
10 def initialize(depth, title, anchor)
11 @depth = depth
12 @title = title
13 @children = []
14 @anchor = anchor
15 end
16
17 delegate :<<, to: :children
18 end
19
20 def initialize(source_html)
21 @source_html = source_html
22 @processed = false
23 @target_html = ''
24 @headers = []
25 @slugs = Hash.new { |h, k| h[k] = 0 }
26 end
27
28 def html
29 parse_and_transform unless @processed
30 @target_html
31 end
32
33 def toc
34 parse_and_transform unless @processed
35 @headers
36 end
37
38 private
39
40 def parse_and_transform
41 return if @source_html.blank?
42
43 parsed_html = Nokogiri::HTML.fragment(@source_html)
44
45 parsed_html.traverse do |node|
46 next unless TARGET_ELEMENTS.include?(node.name)
47
48 anchor = node['id'] || node.text.parameterize
49 @slugs[anchor] += 1
50 anchor = "#{anchor}-#{@slugs[anchor]}" if @slugs[anchor] > 1
51
52 node['id'] = anchor
53
54 next unless LISTED_ELEMENTS.include?(node.name)
55
56 depth = node.name[1..-1]
57 latest_section = @headers.last
58
59 if latest_section.nil? || latest_section.depth >= depth
60 @headers << Section.new(depth, node.text, anchor)
61 else
62 latest_section << Section.new(depth, node.text, anchor)
63 end
64 end
65
66 @target_html = parsed_html.to_s
67 @processed = true
68 end
69 end
This page took 0.149695 seconds and 4 git commands to generate.