<commit_before>#
# HTML emails don't support CSS, so we can use nokogiri to inline attributes based on
# matchers.
#
module Email
  class Styles
    @@plugin_callbacks = []

    attr_accessor :fragment

    delegate :css, to: :fragment

    def initialize(html, opts=nil)
      @html = html
      @opts = opts || {}
      @fragment = Nokogiri::HTML.fragment(@html)
    end

    def self.register_plugin_style(&block)
      @@plugin_callbacks.push(block)
    end

    def add_styles(node, new_styles)
      existing = node['style']
      if existing.present?
        # merge styles
        node['style'] = "#{new_styles}; #{existing}"
      else
        node['style'] = new_styles
      end
    end

    def format_basic
      uri = URI(Discourse.base_url)

      # images
      @fragment.css('img').each do |img|
        next if img['class'] == 'site-logo'

        if img['class'] == "emoji" || img['src'] =~ /(plugins|images)\/emoji/
          img['width'] = 20
          img['height'] = 20
        else
          # use dimensions of original iPhone screen for 'too big, let device rescale'
          if img['width'].to_i > 320 or img['height'].to_i > 480
            img['width'] = 'auto'
            img['height'] = 'auto'
          end
        end

        # ensure all urls are absolute
        if img['src'] =~ /^\/[^\/]/
          img['src'] = "#{Discourse.base_url}#{img['src']}"
        end

        # ensure no schemaless urls
        if img['src'] && img['src'].starts_with?("//")
          img['src'] = "#{uri.scheme}:#{img['src']}"
        end
      end

      # add max-width to big images
      big_images = @fragment.css('img[width="auto"][height="auto"]') -
                   @fragment.css('aside.onebox img') -
                   @fragment.css('img.site-logo, img.emoji')
      big_images.each do |img|
        add_styles(img, 'max-width: 100%;') if img['style'] !~ /max-width/
      end

      # attachments
      @fragment.css('a.attachment').each do |a|
        # ensure all urls are absolute
        if a['href'] =~ /^\/[^\/]/
          a['href'] = "#{Discourse.base_url}#{a['href']}"
        end

        # ensure no schemaless urls
        if a['href'] && a['href'].starts_with?("//")
          a['href'] = "#{uri.scheme}:#{a['href']}"
        end
      end
    end

    def format_notification
      style('.previous-discussion', 'font-size: 17px; color: #444; margin-bottom:10px;')
      style('.notification-date', "text-align:right;color:#999999;padding-right:5px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;font-size:11px")
      style('.username', "font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;color:#3b5998;text-decoration:none;font-weight:bold")
      style('.user-title', "font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;text-decoration:none;margin-left:7px;color: #999;")
      style('.user-name', "font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;text-decoration:none;margin-left:7px;color: #3b5998;font-weight:normal;")
      style('.post-wrapper', "margin-bottom:25px;")
      style('.user-avatar', 'vertical-align:top;width:55px;')
      style('.user-avatar img', nil, width: '45', height: '45')
      style('hr', 'background-color: #ddd; height: 1px; border: 1px;')
      style('.rtl', 'direction: rtl;')
      style('td.body', 'padding-top:5px;', colspan: "2")
      style('.whisper td.body', 'font-style: italic; color: #9c9c9c;')
      style('.lightbox-wrapper .meta', 'display: none')
      correct_first_body_margin
      correct_footer_style
      reset_tables
      onebox_styles
      plugin_styles
    end

    def onebox_styles
      # Links to other topics
      style('aside.quote', 'border-left: 5px solid #e9e9e9; background-color: #f8f8f8; padding: 12px 25px 2px 12px; margin-bottom: 10px;')
      style('aside.quote blockquote', 'border: 0px; padding: 0; margin: 7px 0; background-color: clear;')
      style('aside.quote blockquote > p', 'padding: 0;')
      style('aside.quote div.info-line', 'color: #666; margin: 10px 0')
      style('aside.quote .avatar', 'margin-right: 5px; width:20px; height:20px')

      style('blockquote', 'border-left: 5px solid #e9e9e9; background-color: #f8f8f8; margin: 0;')
      style('blockquote > p', 'padding: 1em;')

      # Oneboxes
      style('aside.onebox', "padding: 12px 25px 2px 12px; border-left: 5px solid #bebebe; background: #eee; margin-bottom: 10px;")
      style('aside.onebox img', "max-height: 80%; max-width: 25%; height: auto; float: left; margin-right: 10px; margin-bottom: 10px")
      style('aside.onebox h3', "border-bottom: 0")
      style('aside.onebox .source', "margin-bottom: 8px")
      style('aside.onebox .source a[href]', "color: #333; font-weight: normal")
      style('aside.clearfix', "clear: both")

      # Finally, convert all `aside` tags to `div`s
      @fragment.css('aside, article, header').each do |n|
        n.name = "div"
      end

      # iframes can't go in emails, so replace them with clickable links
      @fragment.css('iframe').each do |i|
        begin
          # sometimes, iframes are blacklisted...
          if i["src"].blank?
            i.remove
            next
          end

          src_uri = URI(i['src'])
          # If an iframe is protocol relative, use SSL when displaying it
          display_src = "#{src_uri.scheme || 'https'}://#{src_uri.host}#{src_uri.path}#{src_uri.query.nil? ? '' : '?' + src_uri.query}#{src_uri.fragment.nil? ? '' : '#' + src_uri.fragment}"
          i.replace "<p><a href='#{src_uri.to_s}'>#{CGI.escapeHTML(display_src)}</a><p>"
        rescue URI::InvalidURIError
          # If the URL is weird, remove the iframe
          i.remove<commit_msg>UX: fix onebox styling in emails<commit_after>#
# HTML emails don't support CSS, so we can use nokogiri to inline attributes based on
# matchers.
#
module Email
  class Styles
    @@plugin_callbacks = []

    attr_accessor :fragment

    delegate :css, to: :fragment

    def initialize(html, opts=nil)
      @html = html
      @opts = opts || {}
      @fragment = Nokogiri::HTML.fragment(@html)
    end

    def self.register_plugin_style(&block)
      @@plugin_callbacks.push(block)
    end

    def add_styles(node, new_styles)
      existing = node['style']
      if existing.present?
        # merge styles
        node['style'] = "#{new_styles}; #{existing}"
      else
        node['style'] = new_styles
      end
    end

    def format_basic
      uri = URI(Discourse.base_url)

      # images
      @fragment.css('img').each do |img|
        next if img['class'] == 'site-logo'

        if img['class'] == "emoji" || img['src'] =~ /(plugins|images)\/emoji/
          img['width'] = 20
          img['height'] = 20
        else
          # use dimensions of original iPhone screen for 'too big, let device rescale'
          if img['width'].to_i > 320 or img['height'].to_i > 480
            img['width'] = 'auto'
            img['height'] = 'auto'
          end
        end

        # ensure all urls are absolute
        if img['src'] =~ /^\/[^\/]/
          img['src'] = "#{Discourse.base_url}#{img['src']}"
        end

        # ensure no schemaless urls
        if img['src'] && img['src'].starts_with?("//")
          img['src'] = "#{uri.scheme}:#{img['src']}"
        end
      end

      # add max-width to big images
      big_images = @fragment.css('img[width="auto"][height="auto"]') -
                   @fragment.css('aside.onebox img') -
                   @fragment.css('img.site-logo, img.emoji')
      big_images.each do |img|
        add_styles(img, 'max-width: 100%;') if img['style'] !~ /max-width/
      end

      # attachments
      @fragment.css('a.attachment').each do |a|
        # ensure all urls are absolute
        if a['href'] =~ /^\/[^\/]/
          a['href'] = "#{Discourse.base_url}#{a['href']}"
        end

        # ensure no schemaless urls
        if a['href'] && a['href'].starts_with?("//")
          a['href'] = "#{uri.scheme}:#{a['href']}"
        end
      end
    end

    def format_notification
      style('.previous-discussion', 'font-size: 17px; color: #444; margin-bottom:10px;')
      style('.notification-date', "text-align:right;color:#999999;padding-right:5px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;font-size:11px")
      style('.username', "font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;color:#3b5998;text-decoration:none;font-weight:bold")
      style('.user-title', "font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;text-decoration:none;margin-left:7px;color: #999;")
      style('.user-name', "font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;text-decoration:none;margin-left:7px;color: #3b5998;font-weight:normal;")
      style('.post-wrapper', "margin-bottom:25px;")
      style('.user-avatar', 'vertical-align:top;width:55px;')
      style('.user-avatar img', nil, width: '45', height: '45')
      style('hr', 'background-color: #ddd; height: 1px; border: 1px;')
      style('.rtl', 'direction: rtl;')
      style('td.body', 'padding-top:5px;', colspan: "2")
      style('.whisper td.body', 'font-style: italic; color: #9c9c9c;')
      style('.lightbox-wrapper .meta', 'display: none')
      correct_first_body_margin
      correct_footer_style
      reset_tables
      onebox_styles
      plugin_styles
    end

    def onebox_styles
      # Links to other topics
      style('aside.quote', 'border-left: 5px solid #e9e9e9; background-color: #f8f8f8; padding: 12px 25px 2px 12px; margin-bottom: 10px;')
      style('aside.quote blockquote', 'border: 0px; padding: 0; margin: 7px 0; background-color: clear;')
      style('aside.quote blockquote > p', 'padding: 0;')
      style('aside.quote div.info-line', 'color: #666; margin: 10px 0')
      style('aside.quote .avatar', 'margin-right: 5px; width:20px; height:20px')

      style('blockquote', 'border-left: 5px solid #e9e9e9; background-color: #f8f8f8; margin: 0;')
      style('blockquote > p', 'padding: 1em;')

      # Oneboxes
      style('aside.onebox', "border: 5px solid #e9e9e9; padding: 12px 25px 12px 12px;")
      style('aside.onebox header a[href]', "color: #222222; text-decoration: none;")
      style('aside.onebox .onebox-body', "clear: both")
      style('aside.onebox .onebox-body img', "max-height: 80%; max-width: 20%; height: auto; float: left; margin-right: 10px;")
      style('aside.onebox .onebox-body h3, aside.onebox .onebox-body h4', "font-size: 1.17em; margin: 10px 0;")
      style('.onebox-metadata', "color: #919191")

      # Finally, convert all `aside` tags to `div`s
      @fragment.css('aside, article, header').each do |n|
        n.name = "div"
      end

      # iframes can't go in emails, so replace them with clickable links
      @fragment.css('iframe').each do |i|
        begin
          # sometimes, iframes are blacklisted...
          if i["src"].blank?
            i.remove
            next
          end

          src_uri = URI(i['src'])
          # If an iframe is protocol relative, use SSL when displaying it
          display_src = "#{src_uri.scheme || 'https'}://#{src_uri.host}#{src_uri.path}#{src_uri.query.nil? ? '' : '?' + src_uri.query}#{src_uri.fragment.nil? ? '' : '#' + src_uri.fragment}"
          i.replace "<p><a href='#{src_uri.to_s}'>#{CGI.escapeHTML(display_src)}</a><p>"
        rescue URI::InvalidURIError
          # If the URL is weird, remove the iframe
          i.remove
{'fraction_non_alphanumeric': 0.16796942852179955, 'fraction_numerical': 0.023797116553760638, 'mean_word_length': 2.7766480813381436, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '724686', 'n_tokens_mistral': 4088, 'n_tokens_neox': 3795, 'n_words': 838}