diff --git a/app/assets/javascripts/pretty-text/oneboxer.js.es6 b/app/assets/javascripts/pretty-text/oneboxer.js.es6 index ac84ca0d1ac..7dbc43af656 100644 --- a/app/assets/javascripts/pretty-text/oneboxer.js.es6 +++ b/app/assets/javascripts/pretty-text/oneboxer.js.es6 @@ -3,6 +3,38 @@ const loadingQueue = []; const localCache = {}; const failedCache = {}; +function resolveSize(img) { + $(img).addClass('size-resolved'); + + if (img.width > 0 && img.width === img.height) { + $(img).addClass('onebox-avatar'); + } +} + +// Detect square images and apply smaller onebox-avatar class +function applySquareGenericOnebox($elem, normalizedUrl) { + if (!$elem.hasClass('whitelistedgeneric')) { + return; + } + + let $img = $elem.find('.onebox-body img.thumbnail'); + let img = $img[0]; + + // already resolved... skip + if ($img.length !== 1 || $img.hasClass('size-resolved')) { + return; + } + + if (img.complete) { + resolveSize(img, $elem, normalizedUrl); + } else { + $img.on('load.onebox', () => { + resolveSize(img, $elem, normalizedUrl); + $img.off('load.onebox'); + }); + } +} + function loadNext(ajax) { if (loadingQueue.length === 0) { timeout = null; @@ -19,8 +51,11 @@ function loadNext(ajax) { data: { url, refresh, user_id: userId }, cache: true }).then(html => { - localCache[normalize(url)] = html; - $elem.replaceWith(html); + let $html = $(html); + + localCache[normalize(url)] = $html; + $elem.replaceWith($html); + applySquareGenericOnebox($html, normalize(url)); }, result => { if (result && result.jqXHR && result.jqXHR.status === 429) { timeoutMs = 2000; @@ -53,7 +88,7 @@ export function load(e, refresh, ajax, userId, synchronous) { if (!refresh) { // If we have it in our cache, return it. const cached = localCache[normalize(url)]; - if (cached) return cached; + if (cached) return cached.prop('outerHTML'); // If the request failed, don't do anything const failed = failedCache[normalize(url)]; @@ -81,5 +116,6 @@ function normalize(url) { } export function lookupCache(url) { - return localCache[normalize(url)]; + const cached = localCache[normalize(url)]; + return cached && cached.prop('outerHTML'); } diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 781c2cb038a..1f413c07ec3 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -7,7 +7,7 @@ require_dependency 'pretty_text' class CookedPostProcessor include ActionView::Helpers::NumberHelper - attr_reader :cooking_options + attr_reader :cooking_options, :doc def initialize(post, opts = {}) @dirty = false @@ -180,7 +180,6 @@ class CookedPostProcessor # FastImage fails when there's no scheme absolute_url = SiteSetting.scheme + ":" + absolute_url if absolute_url.start_with?("//") - return unless is_valid_image_url?(absolute_url) # we can *always* crawl our own images @@ -331,6 +330,24 @@ class CookedPostProcessor parent_class = img.parent && img.parent["class"] if parent_class&.include?("onebox-body") && (width = img["width"].to_i) > 0 && (height = img["height"].to_i) > 0 + + # special instruction for width == height, assume we are dealing with an avatar + if (img["width"].to_i == img["height"].to_i) + found = false + parent = img + while parent = parent.parent + if parent["class"].include? "whitelistedgeneric" + found = true + break + end + end + + if found + img["class"] = img["class"].to_s + " onebox-avatar" + next + end + end + img.delete('width') img.delete('height') new_parent = img.add_next_sibling("
") diff --git a/lib/final_destination.rb b/lib/final_destination.rb index b290cf83768..ed821d2465d 100644 --- a/lib/final_destination.rb +++ b/lib/final_destination.rb @@ -40,9 +40,7 @@ class FinalDestination @opts[:max_redirects] ||= 5 @opts[:lookup_ip] ||= lambda do |host| begin - IPSocket::getaddress(host) - rescue SocketError - nil + FinalDestination.lookup_ip(host) end end @ignored = [Discourse.base_url_no_prefix] + (@opts[:ignore_redirects] || []) @@ -272,7 +270,13 @@ class FinalDestination end def self.lookup_ip(host) + # TODO clean this up in the test suite, cause it is a mess + # if Rails.env == "test" + # STDERR.puts "WARNING FinalDestination.lookup_ip was called with host: #{host}, this is network call that should be mocked" + # end IPSocket::getaddress(host) + rescue SocketError + nil end end diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb index 72476494175..0034212b9ac 100644 --- a/spec/components/cooked_post_processor_spec.rb +++ b/spec/components/cooked_post_processor_spec.rb @@ -431,14 +431,45 @@ describe CookedPostProcessor do .returns("
GANGNAM STYLE
") cpp.post_process_oneboxes end - - it "is dirty" do - expect(cpp).to be_dirty - end - it "inserts the onebox without wrapping p" do + expect(cpp).to be_dirty expect(cpp.html).to match_html "
GANGNAM STYLE
" end + end + + context ".post_process_oneboxes with square image" do + + it "generates a onebox-avatar class" do + SiteSetting.crawl_images = true + + url = 'https://square-image.com/onebox' + + body = <<~HTML + + + + + + + + HTML + + stub_request(:head, url).to_return(status: 200) + stub_request(:get , url).to_return(status: 200, body: body) + FinalDestination.stubs(:lookup_ip).returns('1.2.3.4') + + # not an ideal stub but shipping the whole image to fast image can add + # a lot of cost to this test + FastImage.stubs(:size).returns([200, 200]) + + post = Fabricate.build(:post, raw: url) + cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) + + cpp.post_process_oneboxes + + expect(cpp.doc.to_s).not_to include('aspect-image') + expect(cpp.doc.to_s).to include('onebox-avatar') + end end