# frozen_string_literal: true class VanillaBodyParser def self.configure(lookup:, uploader:, host:, uploads_path:) @@lookup = lookup @@uploader = uploader @@host = host @@uploads_path = uploads_path end def initialize(row, user_id) @row = row @user_id = user_id end def parse return clean_up(@row['Body']) unless rich? full_text = json.each_with_index.map(&method(:parse_fragment)).join('') normalize full_text end private def clean_up(text) text.gsub(/<\/?font[^>]*>/, '').gsub(/<\/?span[^>]*>/, '').gsub(/<\/?div[^>]*>/, '').gsub(/^ +/, '').gsub(/ +/, ' ') end def rich? @row['Format'].casecmp?('Rich') end def json return nil unless rich? @json ||= JSON.parse(@row['Body']).map(&:deep_symbolize_keys) end def parse_fragment(fragment, index) text = fragment.keys.one? && fragment[:insert].is_a?(String) ? fragment[:insert] : rich_parse(fragment) text = parse_code(text, fragment, index) text = parse_list(text, fragment, index) text end def rich_parse(fragment) insert = fragment[:insert] return parse_mention(insert[:mention]) if insert.respond_to?(:dig) && insert.dig(:mention, :userID) return parse_formatting(fragment) if fragment[:attributes] embed_type = insert.dig(:'embed-external', :data, :embedType) quoting = embed_type == 'quote' return parse_quote(insert) if quoting embed = embed_type.in? ['image', 'link', 'file'] parse_embed(insert) if embed end def parse_mention(mention) user = user_from_imported_id(mention[:userID]) username = user&.username || mention[:name] "@#{username}" end def user_from_imported_id(imported_id) user_id = @@lookup.user_id_from_imported_user_id(imported_id) User.find(user_id) if user_id end def parse_formatting(fragment) insert = fragment[:insert] attributes = fragment[:attributes] text = fragment[:insert] text = "#{text}" if attributes[:link] text = "#{text}" if attributes[:italic] text = "#{text}" if attributes[:bold] text end # In the Quill format used by Vanilla Forums, a line is rendered as `code` # when it's followed by a fragment with attributes: {'code-block': true}. # So we open our ``` block when the next fragment has a 'code-block' # attribute and the previous one didn't and we close the ``` block when # the second next fragment does not contain the 'code-block' attribute def parse_code(text, fragment, index) next_fragment = next_fragment(index) next_code = next_fragment.dig(:attributes, :'code-block') if next_code previous_fragment = previous_fragment(index) previous_code = previous_fragment.dig(:attributes, :'code-block') # if next is code and previous is not, prepend ``` text = "\n```#{text}" unless previous_code end current_code = fragment.dig(:attributes, :'code-block') if current_code second_next_fragment = second_next_fragment(index) second_next_code = second_next_fragment.dig(:attributes, :'code-block') # if current is code and 2 after is not, prepend ``` text = "\n```#{text}" unless second_next_code end text end def parse_list(text, fragment, index) next_fragment = next_fragment(index) next_list = next_fragment.dig(:attributes, :list, :type) if next_list # if next is list, prepend