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