diff options
author | Thibaut Girka <thib@sitedethib.com> | 2019-09-30 12:23:57 +0200 |
---|---|---|
committer | Thibaut Girka <thib@sitedethib.com> | 2019-09-30 12:23:57 +0200 |
commit | 16ff7c5627c12a0c9658e9d2fac7c48002e1b788 (patch) | |
tree | 465a73fb9f42bc2b01127b2d477b0715fb6185b4 /app/lib/toc_generator.rb | |
parent | febcdad2e2c98aee62b55ee21bdf0debf7c6fd6b (diff) | |
parent | 3babf8464b0903b854ec16d355909444ef3ca0bc (diff) |
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - Gemfile - Gemfile.lock - app/controllers/about_controller.rb - app/controllers/auth/sessions_controller.rb
Diffstat (limited to 'app/lib/toc_generator.rb')
-rw-r--r-- | app/lib/toc_generator.rb | 69 |
1 files changed, 69 insertions, 0 deletions
diff --git a/app/lib/toc_generator.rb b/app/lib/toc_generator.rb new file mode 100644 index 000000000..0c8f766ca --- /dev/null +++ b/app/lib/toc_generator.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +class TOCGenerator + TARGET_ELEMENTS = %w(h1 h2 h3 h4 h5 h6).freeze + LISTED_ELEMENTS = %w(h2 h3).freeze + + class Section + attr_accessor :depth, :title, :children, :anchor + + def initialize(depth, title, anchor) + @depth = depth + @title = title + @children = [] + @anchor = anchor + end + + delegate :<<, to: :children + end + + def initialize(source_html) + @source_html = source_html + @processed = false + @target_html = '' + @headers = [] + @slugs = Hash.new { |h, k| h[k] = 0 } + end + + def html + parse_and_transform unless @processed + @target_html + end + + def toc + parse_and_transform unless @processed + @headers + end + + private + + def parse_and_transform + return if @source_html.blank? + + parsed_html = Nokogiri::HTML.fragment(@source_html) + + parsed_html.traverse do |node| + next unless TARGET_ELEMENTS.include?(node.name) + + anchor = node['id'] || node.text.parameterize.presence || 'sec' + @slugs[anchor] += 1 + anchor = "#{anchor}-#{@slugs[anchor]}" if @slugs[anchor] > 1 + + node['id'] = anchor + + next unless LISTED_ELEMENTS.include?(node.name) + + depth = node.name[1..-1] + latest_section = @headers.last + + if latest_section.nil? || latest_section.depth >= depth + @headers << Section.new(depth, node.text, anchor) + else + latest_section << Section.new(depth, node.text, anchor) + end + end + + @target_html = parsed_html.to_s + @processed = true + end +end |