diff --git a/app/assets/images/border1.png b/app/assets/images/border1.png new file mode 100644 index 000000000..0ddc70405 Binary files /dev/null and b/app/assets/images/border1.png differ diff --git a/app/assets/images/border2.png b/app/assets/images/border2.png new file mode 100644 index 000000000..aa62a0b72 Binary files /dev/null and b/app/assets/images/border2.png differ diff --git a/app/assets/images/loading.gif b/app/assets/images/loading.gif new file mode 100644 index 000000000..602ce3c3a Binary files /dev/null and b/app/assets/images/loading.gif differ diff --git a/app/assets/javascripts/discourse.js.coffee b/app/assets/javascripts/discourse.js.coffee index 7bf4eab46..fa184897f 100644 --- a/app/assets/javascripts/discourse.js.coffee +++ b/app/assets/javascripts/discourse.js.coffee @@ -134,6 +134,7 @@ window.Discourse = Ember.Application.createWithMixins return if href is '#' return if $currentTarget.attr('target') return if $currentTarget.data('auto-route') + return if $currentTarget.hasClass('lightbox') return if href.indexOf("mailto:") is 0 if href.match(/^http[s]?:\/\//i) && !href.match new RegExp("^http:\\/\\/" + window.location.hostname,"i") diff --git a/app/assets/javascripts/discourse/components/click_track.js.coffee b/app/assets/javascripts/discourse/components/click_track.js.coffee index ad5c3d406..1d6079d27 100644 --- a/app/assets/javascripts/discourse/components/click_track.js.coffee +++ b/app/assets/javascripts/discourse/components/click_track.js.coffee @@ -6,6 +6,8 @@ window.Discourse.ClickTrack = $a = $(e.currentTarget) + return if $a.hasClass('lightbox') + e.preventDefault() # We don't track clicks on quote back buttons diff --git a/app/assets/javascripts/discourse/components/lightbox.js.coffee b/app/assets/javascripts/discourse/components/lightbox.js.coffee new file mode 100644 index 000000000..7506c7383 --- /dev/null +++ b/app/assets/javascripts/discourse/components/lightbox.js.coffee @@ -0,0 +1,8 @@ +# Helper object for light boxes. Uses highlight.js which is loaded +# on demand. +window.Discourse.Lightbox = + + apply: ($elem) -> + $('a.lightbox', $elem).each (i, e) => + $LAB.script("/javascripts/jquery.colorbox-min.js").wait -> + $(e).colorbox() diff --git a/app/assets/javascripts/discourse/views/post_view.js.coffee b/app/assets/javascripts/discourse/views/post_view.js.coffee index af0864030..8a5c787b8 100644 --- a/app/assets/javascripts/discourse/views/post_view.js.coffee +++ b/app/assets/javascripts/discourse/views/post_view.js.coffee @@ -212,6 +212,7 @@ window.Discourse.PostView = Ember.View.extend # Add syntax highlighting Discourse.SyntaxHighlighting.apply($post) + Discourse.Lightbox.apply($post) # If we're scrolling upwards, adjust the scroll position accordingly if scrollTo = @get('post.scrollTo') diff --git a/app/assets/stylesheets/application/lightbox.scss b/app/assets/stylesheets/application/lightbox.scss new file mode 100644 index 000000000..582f63efd --- /dev/null +++ b/app/assets/stylesheets/application/lightbox.scss @@ -0,0 +1,50 @@ +/* + ColorBox Core Style: + The following CSS is consistent between example themes and should not be altered. +*/ +#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} +#cboxOverlay{position:fixed; width:100%; height:100%;} +#cboxMiddleLeft, #cboxBottomLeft{clear:left;} +#cboxContent{position:relative;} +#cboxLoadedContent{overflow:auto; -webkit-overflow-scrolling: touch;} +#cboxTitle{margin:0;} +#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;} +#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} +.cboxPhoto{float:left; margin:auto; border:0; display:block; max-width:none;} +.cboxIframe{width:100%; height:100%; display:block; border:0;} +#colorbox, #cboxContent, #cboxLoadedContent{box-sizing:content-box; -moz-box-sizing:content-box; -webkit-box-sizing:content-box;} + +/* + User Style: + Change the following styles to modify the appearance of ColorBox. They are + ordered & tabbed in a way that represents the nesting of the generated HTML. +*/ +#cboxOverlay{background:#fff;} +#colorbox{outline:0;} + #cboxTopLeft{width:25px; height:25px; background:image-url("images/border1.png") no-repeat 0 0;} + #cboxTopCenter{height:25px; background:image-url("images/border1.png") repeat-x 0 -50px;} + #cboxTopRight{width:25px; height:25px; background:image-url("images/border1.png") no-repeat -25px 0;} + #cboxBottomLeft{width:25px; height:25px; background:image-url("images/border1.png") no-repeat 0 -25px;} + #cboxBottomCenter{height:25px; background:image-url("images/border1.png") repeat-x 0 -75px;} + #cboxBottomRight{width:25px; height:25px; background:image-url("images/border1.png") no-repeat -25px -25px;} + #cboxMiddleLeft{width:25px; background:image-url("images/border2.png") repeat-y 0 0;} + #cboxMiddleRight{width:25px; background:image-url("images/border2.png") repeat-y -25px 0;} + #cboxContent{background:#fff; overflow:hidden;} + .cboxIframe{background:#fff;} + #cboxError{padding:50px; border:1px solid #ccc;} + #cboxLoadedContent{margin-bottom:20px;} + #cboxTitle{position:absolute; bottom:0px; left:0; text-align:center; width:100%; color:#999;} + #cboxCurrent{position:absolute; bottom:0px; left:100px; color:#999;} + #cboxLoadingOverlay{background:#fff image-url("images/loading.gif") no-repeat 5px 5px;} + + /* these elements are buttons, and may need to have additional styles reset to avoid unwanted base styles */ + #cboxPrevious, #cboxNext, #cboxSlideshow, #cboxClose {border:0; padding:0; margin:0; overflow:visible; width:auto; background:none; } + + /* avoid outlines on :active (mouseclick), but preserve outlines on :focus (tabbed navigating) */ + #cboxPrevious:active, #cboxNext:active, #cboxSlideshow:active, #cboxClose:active {outline:0;} + + #cboxSlideshow{position:absolute; bottom:0px; right:42px; color:#444;} + #cboxPrevious{position:absolute; bottom:0px; left:0; color:#444;} + #cboxNext{position:absolute; bottom:0px; left:63px; color:#444;} + #cboxClose{position:absolute; bottom:0; right:0; display:block; color:#444;} + diff --git a/app/models/post.rb b/app/models/post.rb index 520d2ce74..80ff1fc9f 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -14,12 +14,10 @@ class Post < ActiveRecord::Base FLAG_THRESHOLD_REACHED_AGAIN = 2 end - versioned - rate_limit - acts_as_paranoid + after_recover :update_flagged_posts_count after_destroy :update_flagged_posts_count diff --git a/app/models/site_customization.rb b/app/models/site_customization.rb index fe338c8ff..492f27827 100644 --- a/app/models/site_customization.rb +++ b/app/models/site_customization.rb @@ -107,7 +107,7 @@ footer:after{ content: '#{error}' }" @lock.synchronize do style = self.where(key: key).first - style.ensure_stylesheet_on_disk! + style.ensure_stylesheet_on_disk! if style @cache[key] = style end end diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index 87d2aaae7..4146d7cd4 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -81,7 +81,7 @@ class SiteSetting < ActiveRecord::Base setting(:max_flags_per_day, 20) setting(:max_edits_per_day, 30) setting(:max_favorites_per_day, 20) - + setting(:auto_link_images_wider_than, 50) setting(:email_time_window_mins, 5) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 64e39f82c..880a75801 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -338,6 +338,8 @@ en: basic_requires_read_posts: "How many posts a user needs to have read to be promoted to basic level" basic_requires_time_spent_mins: "How many mins a user needs to have spent reading posts to be promoted to basic level" + auto_link_images_wider_than: "How wide does an image need to be, to be considered for auto link and lightbox treatment" + email_time_window_mins: "How many minutes we wait before sending a user mail, to give them a chance to see it first." flush_timings_secs: "How frequently we flush timing data to the server, in seconds." diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 25bba7176..99d462498 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -5,11 +5,13 @@ require_dependency 'oneboxer' class CookedPostProcessor + def initialize(post, opts={}) @dirty = false @opts = opts @post = post @doc = Nokogiri::HTML(post.cooked) + @size_cache = {} end def dirty? @@ -33,42 +35,82 @@ class CookedPostProcessor # First let's consider the images def post_process_images images = @doc.search("img") - if images.present? + return unless images.present? - # Extract the first image from the first post and use it as the 'topic image' - if @post.post_number == 1 - img = images.first - @post.topic.update_column :image_url, img['src'] if img['src'].present? - end + # Extract the first image from the first post and use it as the 'topic image' + if @post.post_number == 1 + img = images.first + @post.topic.update_column :image_url, img['src'] if img['src'].present? + end - images.each do |img| - if img['src'].present? + images.each do |img| + src = img['src'] + src = Discourse.base_url + src if src[0] == "/" - # If we provided some image sizes, look those up first - if @opts[:image_sizes].present? - if dim = @opts[:image_sizes][img['src']] - w, h = ImageSizer.resize(dim['width'], dim['height']) - img.set_attribute 'width', w.to_s - img.set_attribute 'height', h.to_s - @dirty = true - end - end + if src.present? && (img['width'].blank? || img['height'].blank?) - # If the image has no width or height, figure them out. - if img['width'].blank? or img['height'].blank? - dim = CookedPostProcessor.image_dimensions(img['src']) - if dim.present? - img.set_attribute 'width', dim[0].to_s - img.set_attribute 'height', dim[1].to_s - @dirty = true - end - end + w,h = + get_size_from_image_sizes(src, @opts[:image_sizes]) || + image_dimensions(src) + if w && h + img['width'] = w.to_s + img['height'] = h.to_s + @dirty = true end end - end + + if src.present? + if src != img['src'] + img['src'] = src + @dirty = true + end + convert_to_link!(img) + img.set_attribute('src', optimize_image(src)) + end + + end end + def optimize_image(src) + src + end + + def convert_to_link!(img) + src = img["src"] + width = img["width"].to_i + height = img["height"].to_i + + return unless src.present? && width > SiteSetting.auto_link_images_wider_than + + original_width, original_height = get_size(src) + + return unless original_width.to_i > width && original_height.to_i > height + + parent = img.parent + while parent + return if parent.name == "a" + break unless parent.respond_to? :parent + parent = parent.parent + end + + # not a hyperlink so we can apply + a = Nokogiri::XML::Node.new "a", @doc + img.add_next_sibling(a) + a["href"] = src + a["class"] = "lightbox" + a.add_child(img) + @dirty = true + + end + + def get_size_from_image_sizes(src, image_sizes) + if image_sizes.present? + if dim = image_sizes[src] + ImageSizer.resize(dim['width'], dim['height']) + end + end + end def post_process return unless @doc.present? @@ -80,14 +122,18 @@ class CookedPostProcessor @doc.try(:to_html) end - # Retrieve the image dimensions for a url - def self.image_dimensions(url) - return nil unless SiteSetting.crawl_images? - uri = URI.parse(url) - return nil unless %w(http https).include?(uri.scheme) - w, h = FastImage.size(url) + def get_size(url) + return nil unless SiteSetting.crawl_images? || url.start_with?(Discourse.base_url) + @size_cache[url] ||= FastImage.size(url) + end - ImageSizer.resize(w, h) + # Retrieve the image dimensions for a url + def image_dimensions(url) + uri = URI.parse(url) + + return nil unless %w(http https).include?(uri.scheme) + w, h = get_size(url) + ImageSizer.resize(w, h) if w && h end end diff --git a/public/javascripts/jquery.colorbox-min.js b/public/javascripts/jquery.colorbox-min.js new file mode 100644 index 000000000..0a3f248a0 --- /dev/null +++ b/public/javascripts/jquery.colorbox-min.js @@ -0,0 +1,6 @@ +/*! + jQuery ColorBox v1.4.3 - 2013-02-18 + (c) 2013 Jack Moore - jacklmoore.com/colorbox + license: http://www.opensource.org/licenses/mit-license.php +*/ +(function(e,t,i){function o(i,o,n){var r=t.createElement(i);return o&&(r.id=Y+o),n&&(r.style.cssText=n),e(r)}function n(e){var t=T.length,i=(A+e)%t;return 0>i?t+i:i}function r(e,t){return Math.round((/%/.test(e)?("x"===t?k.width():k.height())/100:1)*parseInt(e,10))}function h(e,t){return e.photo||e.photoRegex.test(t)}function l(e,t){return e.retinaUrl&&i.devicePixelRatio>1?t.replace(e.photoRegex,e.retinaSuffix):t}function s(e){"contains"in w[0]&&!w[0].contains(e.target)&&(e.stopPropagation(),w.focus())}function a(){var t,i=e.data(N,V);null==i?(K=e.extend({},J),console&&console.log&&console.log("Error: cboxElement missing settings object")):K=e.extend({},i);for(t in K)e.isFunction(K[t])&&"on"!==t.slice(0,2)&&(K[t]=K[t].call(N));K.rel=K.rel||N.rel||e(N).data("rel")||"nofollow",K.href=K.href||e(N).attr("href"),K.title=K.title||N.title,"string"==typeof K.href&&(K.href=e.trim(K.href))}function d(i,o){e(t).trigger(i),at.trigger(i),e.isFunction(o)&&o.call(N)}function c(){var e,t,i,o,n,r=Y+"Slideshow_",h="click."+Y;K.slideshow&&T[1]?(t=function(){clearTimeout(e)},i=function(){(K.loop||T[A+1])&&(e=setTimeout(G.next,K.slideshowSpeed))},o=function(){M.html(K.slideshowStop).unbind(h).one(h,n),at.bind(it,i).bind(tt,t).bind(ot,n),w.removeClass(r+"off").addClass(r+"on")},n=function(){t(),at.unbind(it,i).unbind(tt,t).unbind(ot,n),M.html(K.slideshowStart).unbind(h).one(h,function(){G.next(),o()}),w.removeClass(r+"on").addClass(r+"off")},K.slideshowAuto?o():n()):w.removeClass(r+"off "+r+"on")}function u(i){U||(N=i,a(),T=e(N),A=0,"nofollow"!==K.rel&&(T=e("."+Z).filter(function(){var t,i=e.data(this,V);return i&&(t=e(this).data("rel")||i.rel||this.rel),t===K.rel}),A=T.index(N),-1===A&&(T=T.add(N),A=T.length-1)),m.css({opacity:parseFloat(K.opacity),cursor:K.overlayClose?"pointer":"auto",visibility:"visible"}).show(),j||(j=q=!0,w.css({visibility:"hidden",display:"block"}),E=o(dt,"LoadedContent","width:0; height:0; overflow:hidden").appendTo(v),_=x.height()+C.height()+v.outerHeight(!0)-v.height(),z=y.width()+b.width()+v.outerWidth(!0)-v.width(),D=E.outerHeight(!0),B=E.outerWidth(!0),K.w=r(K.initialWidth,"x"),K.h=r(K.initialHeight,"y"),G.position(),lt&&k.bind("resize."+st+" scroll."+st,function(){m.css({width:k.width(),height:k.height(),top:k.scrollTop(),left:k.scrollLeft()})}).trigger("resize."+st),c(),d(et,K.onOpen),P.add(W).hide(),R.html(K.close).show(),w.focus(),t.addEventListener&&(t.addEventListener("focus",s,!0),at.one(nt,function(){t.removeEventListener("focus",s,!0)})),K.returnFocus&&at.one(nt,function(){e(N).focus()})),G.load(!0))}function f(){!w&&t.body&&(X=!1,k=e(i),w=o(dt).attr({id:V,"class":ht?Y+(lt?"IE6":"IE"):"",role:"dialog",tabindex:"-1"}).hide(),m=o(dt,"Overlay",lt?"position:absolute":"").hide(),L=o(dt,"LoadingOverlay").add(o(dt,"LoadingGraphic")),g=o(dt,"Wrapper"),v=o(dt,"Content").append(W=o(dt,"Title"),H=o(dt,"Current"),F=o("button","Previous"),S=o("button","Next"),M=o("button","Slideshow"),L,R=o("button","Close")),g.append(o(dt).append(o(dt,"TopLeft"),x=o(dt,"TopCenter"),o(dt,"TopRight")),o(dt,!1,"clear:left").append(y=o(dt,"MiddleLeft"),v,b=o(dt,"MiddleRight")),o(dt,!1,"clear:left").append(o(dt,"BottomLeft"),C=o(dt,"BottomCenter"),o(dt,"BottomRight"))).find("div div").css({"float":"left"}),I=o(dt,!1,"position:absolute; width:9999px; visibility:hidden; display:none"),P=S.add(F).add(H).add(M),e(t.body).append(m,w.append(g,I)))}function p(){function i(e){e.which>1||e.shiftKey||e.altKey||e.metaKey||(e.preventDefault(),u(this))}return w?(X||(X=!0,S.click(function(){G.next()}),F.click(function(){G.prev()}),R.click(function(){G.close()}),m.click(function(){K.overlayClose&&G.close()}),e(t).bind("keydown."+Y,function(e){var t=e.keyCode;j&&K.escKey&&27===t&&(e.preventDefault(),G.close()),j&&K.arrowKey&&T[1]&&!e.altKey&&(37===t?(e.preventDefault(),F.click()):39===t&&(e.preventDefault(),S.click()))}),e.isFunction(e.fn.on)?e(t).on("click."+Y,"."+Z,i):e("."+Z).live("click."+Y,i)),!0):!1}var m,w,g,v,x,y,b,C,T,k,E,I,L,W,H,M,S,F,R,P,K,_,z,D,B,N,A,O,j,q,U,$,G,Q,X,J={transition:"elastic",speed:300,width:!1,initialWidth:"600",innerWidth:!1,maxWidth:!1,height:!1,initialHeight:"450",innerHeight:!1,maxHeight:!1,scalePhotos:!0,scrolling:!0,inline:!1,html:!1,iframe:!1,fastIframe:!0,photo:!1,href:!1,title:!1,rel:!1,opacity:.9,preloading:!0,className:!1,retinaImage:!1,retinaUrl:!1,retinaSuffix:"@2x.$1",current:"image {current} of {total}",previous:"previous",next:"next",close:"close",xhrError:"This content failed to load.",imgError:"This image failed to load.",open:!1,returnFocus:!0,reposition:!0,loop:!0,slideshow:!1,slideshowAuto:!0,slideshowSpeed:2500,slideshowStart:"start slideshow",slideshowStop:"stop slideshow",photoRegex:/\.(gif|png|jp(e|g|eg)|bmp|ico)((#|\?).*)?$/i,onOpen:!1,onLoad:!1,onComplete:!1,onCleanup:!1,onClosed:!1,overlayClose:!0,escKey:!0,arrowKey:!0,top:!1,bottom:!1,left:!1,right:!1,fixed:!1,data:void 0},V="colorbox",Y="cbox",Z=Y+"Element",et=Y+"_open",tt=Y+"_load",it=Y+"_complete",ot=Y+"_cleanup",nt=Y+"_closed",rt=Y+"_purge",ht=!e.support.leadingWhitespace,lt=ht&&!i.XMLHttpRequest,st=Y+"_IE6",at=e({}),dt="div";e.colorbox||(e(f),G=e.fn[V]=e[V]=function(t,i){var o=this;if(t=t||{},f(),p()){if(e.isFunction(o))o=e(""),t.open=!0;else if(!o[0])return o;i&&(t.onComplete=i),o.each(function(){e.data(this,V,e.extend({},e.data(this,V)||J,t))}).addClass(Z),(e.isFunction(t.open)&&t.open.call(o)||t.open)&&u(o[0])}return o},G.position=function(e,t){function i(e){x[0].style.width=C[0].style.width=v[0].style.width=parseInt(e.style.width,10)-z+"px",v[0].style.height=y[0].style.height=b[0].style.height=parseInt(e.style.height,10)-_+"px"}var o,n,h,l=0,s=0,a=w.offset();k.unbind("resize."+Y),w.css({top:-9e4,left:-9e4}),n=k.scrollTop(),h=k.scrollLeft(),K.fixed&&!lt?(a.top-=n,a.left-=h,w.css({position:"fixed"})):(l=n,s=h,w.css({position:"absolute"})),s+=K.right!==!1?Math.max(k.width()-K.w-B-z-r(K.right,"x"),0):K.left!==!1?r(K.left,"x"):Math.round(Math.max(k.width()-K.w-B-z,0)/2),l+=K.bottom!==!1?Math.max(k.height()-K.h-D-_-r(K.bottom,"y"),0):K.top!==!1?r(K.top,"y"):Math.round(Math.max(k.height()-K.h-D-_,0)/2),w.css({top:a.top,left:a.left,visibility:"visible"}),e=w.width()===K.w+B&&w.height()===K.h+D?0:e||0,g[0].style.width=g[0].style.height="9999px",o={width:K.w+B+z,height:K.h+D+_,top:l,left:s},0===e&&w.css(o),w.dequeue().animate(o,{duration:e,complete:function(){i(this),q=!1,g[0].style.width=K.w+B+z+"px",g[0].style.height=K.h+D+_+"px",K.reposition&&setTimeout(function(){k.bind("resize."+Y,G.position)},1),t&&t()},step:function(){i(this)}})},G.resize=function(e){j&&(e=e||{},e.width&&(K.w=r(e.width,"x")-B-z),e.innerWidth&&(K.w=r(e.innerWidth,"x")),E.css({width:K.w}),e.height&&(K.h=r(e.height,"y")-D-_),e.innerHeight&&(K.h=r(e.innerHeight,"y")),e.innerHeight||e.height||(E.css({height:"auto"}),K.h=E.height()),E.css({height:K.h}),G.position("none"===K.transition?0:K.speed))},G.prep=function(t){function i(){return K.w=K.w||E.width(),K.w=K.mw&&K.mw1?("string"==typeof K.current&&H.html(K.current.replace("{current}",A+1).replace("{total}",s)).show(),S[K.loop||s-1>A?"show":"hide"]().html(K.next),F[K.loop||A?"show":"hide"]().html(K.previous),K.slideshow&&M.show(),K.preloading&&e.each([n(-1),n(1)],function(){var t,i,o=T[this],n=e.data(o,V);n&&n.href?(t=n.href,e.isFunction(t)&&(t=t.call(o))):t=e(o).attr("href"),t&&h(n,t)&&(t=l(n,t),i=new Image,i.src=t)})):P.hide(),K.iframe?(i=o("iframe")[0],c in i&&(i[c]=0),u in i&&(i[u]="true"),K.scrolling||(i.scrolling="no"),e(i).attr({src:K.href,name:(new Date).getTime(),"class":Y+"Iframe",allowFullScreen:!0,webkitAllowFullScreen:!0,mozallowfullscreen:!0}).one("load",r).appendTo(E),at.one(rt,function(){i.src="//about:blank"}),K.fastIframe&&e(i).trigger("load")):r(),"fade"===K.transition?w.fadeTo(a,1,t):t())},"fade"===K.transition?w.fadeTo(a,0,function(){G.position(0,s)}):G.position(a,s)}},G.load=function(t){var n,s,c,u=G.prep;q=!0,O=!1,N=T[A],t||a(),Q&&w.add(m).removeClass(Q),K.className&&w.add(m).addClass(K.className),Q=K.className,d(rt),d(tt,K.onLoad),K.h=K.height?r(K.height,"y")-D-_:K.innerHeight&&r(K.innerHeight,"y"),K.w=K.width?r(K.width,"x")-B-z:K.innerWidth&&r(K.innerWidth,"x"),K.mw=K.w,K.mh=K.h,K.maxWidth&&(K.mw=r(K.maxWidth,"x")-B-z,K.mw=K.w&&K.w1&&(O.height=O.height/i.devicePixelRatio,O.width=O.width/i.devicePixelRatio),K.scalePhotos&&(s=function(){O.height-=O.height*e,O.width-=O.width*e},K.mw&&O.width>K.mw&&(e=(O.width-K.mw)/O.width,s()),K.mh&&O.height>K.mh&&(e=(O.height-K.mh)/O.height,s())),K.h&&(O.style.marginTop=Math.max(K.mh-O.height,0)/2+"px"),T[1]&&(K.loop||T[A+1])&&(O.style.cursor="pointer",O.onclick=function(){G.next()}),ht&&(O.style.msInterpolationMode="bicubic"),setTimeout(function(){u(O)},1)}),setTimeout(function(){O.src=n},1)):n&&I.load(n,K.data,function(t,i){u("error"===i?o(dt,"Error").html(K.xhrError):e(this).contents())})},G.next=function(){!q&&T[1]&&(K.loop||T[A+1])&&(A=n(1),G.load())},G.prev=function(){!q&&T[1]&&(K.loop||A)&&(A=n(-1),G.load())},G.close=function(){j&&!U&&(U=!0,j=!1,d(ot,K.onCleanup),k.unbind("."+Y+" ."+st),m.fadeTo(200,0),w.stop().fadeTo(300,0,function(){w.add(m).css({opacity:1,cursor:"auto"}).hide(),d(rt),E.empty().remove(),setTimeout(function(){U=!1,d(nt,K.onClosed)},1)}))},G.remove=function(){e([]).add(w).add(m).remove(),w=null,e("."+Z).removeData(V).removeClass(Z),e(t).unbind("click."+Y)},G.element=function(){return e(N)},G.settings=J)})(jQuery,document,window); \ No newline at end of file diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb index 96904db65..901a309b2 100644 --- a/spec/components/cooked_post_processor_spec.rb +++ b/spec/components/cooked_post_processor_spec.rb @@ -3,6 +3,11 @@ require 'spec_helper' require 'cooked_post_processor' describe CookedPostProcessor do + let :cpp do + post = Fabricate.build(:post_with_youtube) + post.id = 123 + CookedPostProcessor.new(post) + end context 'process_onebox' do @@ -39,10 +44,11 @@ EXPECTED @topic = Fabricate(:topic) @post = Fabricate.build(:post_with_image_url, topic: @topic, user: @topic.user) @cpp = CookedPostProcessor.new(@post, :image_sizes => {'http://www.forumwarz.com/images/header/logo.png' => {'width' => 111, 'height' => 222}}) + @cpp.expects(:get_size).returns([111,222]) end it "doesn't call image_dimensions because it knows the size" do - CookedPostProcessor.expects(:image_dimensions).never + @cpp.expects(:image_dimensions).never @cpp.post_process_images end @@ -55,7 +61,8 @@ EXPECTED context 'with unsized images in the post' do before do - CookedPostProcessor.expects(:image_dimensions).returns([123, 456]) + FastImage.stubs(:size).returns([123, 456]) + CookedPostProcessor.any_instance.expects(:image_dimensions).returns([123, 456]) @post = Fabricate(:post_with_images) end @@ -72,10 +79,36 @@ EXPECTED end end + context 'link convertor' do + before do + SiteSetting.stubs(:crawl_images?).returns(true) + end + + let :post_with_img do + Fabricate.build(:post, cooked: '

') + end + + let :cpp_for_post do + CookedPostProcessor.new(post_with_img) + end + + it 'convert img tags to links if they are sized down' do + cpp_for_post.expects(:get_size).returns([2000,2000]).twice + cpp_for_post.post_process + cpp_for_post.html.should =~ /a href/ + end + + it 'does not convert img tags to links if they are small' do + cpp_for_post.expects(:get_size).returns([200,200]).twice + cpp_for_post.post_process + (cpp_for_post.html !~ /a href/).should be_true + end + + end context 'image_dimensions' do it "returns unless called with a http or https url" do - CookedPostProcessor.image_dimensions('/tmp/image.jpg').should be_blank + cpp.image_dimensions('/tmp/image.jpg').should be_blank end context 'with valid url' do @@ -86,13 +119,13 @@ EXPECTED it "doesn't call fastimage if image crawling is disabled" do SiteSetting.expects(:crawl_images?).returns(false) FastImage.expects(:size).never - CookedPostProcessor.image_dimensions(@url) + cpp.image_dimensions(@url) end it "calls fastimage if image crawling is enabled" do SiteSetting.expects(:crawl_images?).returns(true) FastImage.expects(:size).with(@url) - CookedPostProcessor.image_dimensions(@url) + cpp.image_dimensions(@url) end end end