diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 339848d33..efff2f0cd 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -29,9 +29,12 @@ class PhotosController < ApplicationController @contacts_of_contact_count = 0 end - @posts = current_user.photos_from(@person).paginate(:page => params[:page]) - - render 'people/show' + @posts = current_user.photos_from(@person) + + respond_to do |format| + format.all { render 'people/show' } + format.json{ render_for_api :backbone, :json => @posts, :root => :photos } + end else flash[:error] = I18n.t 'people.show.does_not_exist' diff --git a/public/javascripts/app/collections/photos.js b/public/javascripts/app/collections/photos.js new file mode 100644 index 000000000..40c960b23 --- /dev/null +++ b/public/javascripts/app/collections/photos.js @@ -0,0 +1,12 @@ +app.collections.Photos = Backbone.Collection.extend({ + url : "/photos", + + model: function(attrs, options) { + var modelClass = app.models.Photo + return new modelClass(attrs, options); + }, + + parse: function(resp){ + return resp.photos; + } +}); diff --git a/public/javascripts/app/models/photo.js b/public/javascripts/app/models/photo.js new file mode 100644 index 000000000..05b4a3c1e --- /dev/null +++ b/public/javascripts/app/models/photo.js @@ -0,0 +1,14 @@ +app.models.Photo = Backbone.Model.extend({ + urlRoot : "/photos", + + initialize : function() {}, + + createdAt : function() { + return this.timeOf("created_at"); + }, + + timeOf: function(field) { + return new Date(this.get(field)) /1000; + }, + +}); \ No newline at end of file diff --git a/public/javascripts/app/models/photos.js b/public/javascripts/app/models/photos.js new file mode 100644 index 000000000..cbffb3de2 --- /dev/null +++ b/public/javascripts/app/models/photos.js @@ -0,0 +1,69 @@ +app.models.Photos = Backbone.Model.extend({ + initialize : function(){ + this.photos = new app.collections.Photos([], this.photoOptions()); + }, + + photoOptions :function(){ + var order = this.sortOrder(); + return { + comparator : function(photo) { return -photo[order](); } + } + }, + + url : function() { + return _.any(this.photos.models) ? this.timeFilteredPath() : this.basePath() + }, + + _fetching : false, + + fetch : function(){ + if(this._fetching) { return false; } + var self = this; + + // we're fetching the collection... there is probably a better way to do this + self._fetching = true; + + this.photos + .fetch({ + add : true, + url : self.url() + }) + .done( + function(resp){ + // we're done fetching... there is probably a better way to handle this + self._fetching = false; + + self.trigger("fetched", self); + + // all loaded? + if(resp.photos && resp.photos.length == 0) { + self.trigger("allPostsLoaded", self); + } + } + ); + + return this; + }, + + basePath : function(){ + return document.location.pathname; + }, + + timeFilteredPath : function(){ + return this.basePath() + "?max_time=" + this.maxTime(); + }, + + maxTime: function(){ + var lastPost = _.last(this.photos.models); + return lastPost[this.sortOrder()]() + }, + + sortOrder : function() { + return "createdAt"; + }, + + add : function(models){ + this.photos.add(models) + } + +}); \ No newline at end of file diff --git a/public/javascripts/app/router.js b/public/javascripts/app/router.js index 736128730..2f6e6dc23 100644 --- a/public/javascripts/app/router.js +++ b/public/javascripts/app/router.js @@ -11,7 +11,8 @@ app.Router = Backbone.Router.extend({ "liked": "stream", "mentions": "stream", "people/:id": "stream", - "u/:name": "stream", + "people/:id/photos": "photos", + "u/:name": "stream", "followed_tags": "stream", "tags/:name": "stream", "posts/:id": "singlePost", @@ -19,7 +20,7 @@ app.Router = Backbone.Router.extend({ }, stream : function() { - app.stream = new app.models.Stream() + app.stream = new app.models.Stream(); app.page = new app.views.Stream({model : app.stream}).render(); app.publisher = app.publisher || new app.views.Publisher({collection : app.stream.posts}); @@ -29,6 +30,13 @@ app.Router = Backbone.Router.extend({ $('#selected_aspect_contacts .content').html(streamFacesView.el); }, + photos : function() { + app.photos = new app.models.Photos(); + app.page = new app.views.Photos({model : app.photos}).render(); + + $("#main_stream").html(app.page.el); + }, + singlePost : function(id) { new app.models.Post({id : id}).fetch({success : function(resp){ var postAttrs = resp.get("posts"); diff --git a/public/javascripts/app/templates/photo.handlebars b/public/javascripts/app/templates/photo.handlebars new file mode 100644 index 000000000..375d6869e --- /dev/null +++ b/public/javascripts/app/templates/photo.handlebars @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/javascripts/app/views/photo_view.js b/public/javascripts/app/views/photo_view.js new file mode 100644 index 000000000..c672863d4 --- /dev/null +++ b/public/javascripts/app/views/photo_view.js @@ -0,0 +1,13 @@ +app.views.Photo = app.views.StreamObject.extend({ + + templateName: "photo", + + className : "photo loaded", + + initialize : function() { + $(this.el).attr("id", this.model.get("guid")); + this.model.bind('remove', this.remove, this); + return this; + } + +}); \ No newline at end of file diff --git a/public/javascripts/app/views/photos_view.js b/public/javascripts/app/views/photos_view.js new file mode 100644 index 000000000..d9161356e --- /dev/null +++ b/public/javascripts/app/views/photos_view.js @@ -0,0 +1,56 @@ +app.views.Photos = Backbone.View.extend({ + + events : {}, + + initialize : function(options) { + this.photos = this.model; + this.collection = this.model.photos; + + this.setupEvents(); + //this.setupLightbox(); ERROR: "imageThumb is undefined" ... + }, + + setupEvents : function(){ + this.photos.bind("fetched", this.removeLoader, this) + this.collection.bind("add", this.addPhoto, this); + }, + + addPhoto : function(photo) { + var photoView = new app.views.Photo({ model: photo }); + + $(this.el)[ + (this.collection.at(0).id == photo.id) + ? "prepend" + : "append" + ](photoView.render().el); + + return this; + }, + + render : function(evt) { + if(evt) {evt.preventDefault(); } + + if(this.model.fetch()) { + this.appendLoader(); + }; + + return this; + }, + + appendLoader: function(){ + $("#paginate").html($("", { + src : "/images/static-loader.png", + "class" : "loader" + })); + }, + + removeLoader: function() { + $("#paginate").empty(); + }, + + setupLightbox : function(){ + this.lightbox = Diaspora.BaseWidget.instantiate("Lightbox"); + $(this.el).delegate("a.photo-link", "click", this.lightbox.lightboxImageClicked); + }, + +}); \ No newline at end of file diff --git a/public/stylesheets/sass/application.sass b/public/stylesheets/sass/application.sass index f0583a407..cee46c3c5 100644 --- a/public/stylesheets/sass/application.sass +++ b/public/stylesheets/sass/application.sass @@ -2821,3 +2821,4 @@ a.toggle_selector :color #999 a :color #666 + :color #666 \ No newline at end of file diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index 1d9e5d491..6279723af 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -80,6 +80,14 @@ describe PhotosController do assigns[:person].should == bob.person assigns[:posts].should == [@bobs_photo] end + + it "returns json when requested" do + request.env['HTTP_ACCEPT'] = 'application/json' + get :index, :person_id => alice.person.guid.to_s + + response.headers['Content-Type'].should match 'application/json.*' + save_fixture(response.body, "photos_json") + end end describe '#show' do diff --git a/spec/javascripts/app/models/photo_spec.js b/spec/javascripts/app/models/photo_spec.js new file mode 100644 index 000000000..d1ccd8703 --- /dev/null +++ b/spec/javascripts/app/models/photo_spec.js @@ -0,0 +1,27 @@ +describe("app.models.Photo", function() { + + beforeEach(function(){ + this.photo = new app.models.Photo(); + }); + + describe("url", function(){ + it("should be /photos when it doesn't have an id", function(){ + expect(new app.models.Photo().url()).toBe("/photos"); + }); + + it("should be /photos/id when it has an id", function(){ + expect(new app.models.Photo({id: 5}).url()).toBe("/photos/5"); + }); + }); + + describe("createdAt", function() { + it("returns the photo's created_at as an integer", function() { + var date = new Date; + this.photo.set({ created_at: +date * 1000 }); + + expect(typeof this.photo.createdAt()).toEqual("number"); + expect(this.photo.createdAt()).toEqual(+date); + }); + }); + +}); diff --git a/spec/javascripts/app/views/photos_view_spec.js b/spec/javascripts/app/views/photos_view_spec.js new file mode 100644 index 000000000..c96a39df8 --- /dev/null +++ b/spec/javascripts/app/views/photos_view_spec.js @@ -0,0 +1,45 @@ +describe("app.views.Photos", function() { + beforeEach(function() { + loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}}); + + this._photos = $.parseJSON(spec.readFixture("photos_json"))["photos"]; + + this.photos = new app.models.Photos(); + this.photos.add(this._photos); + + this.view = new app.views.Photos({model : this.photos}); + + // do this manually because we've moved loadMore into render?? + this.view.render(); + _.each(this.view.collection.models, function(photo) { + this.view.addPhoto(photo); + }, this); + }); + + describe("initialize", function() { + // nothing there yet + }); + + describe("#render", function() { + beforeEach(function() { + this.photo = this.photos.photos.models[0]; + this.photoElement = $(this.view.$("#" + this.photo.get("guid"))); + }); + + context("when rendering a photo message", function() { + it("shows the photo in the content area", function() { + expect(this.photoElement.length).toBeGreaterThan(0); //markdown'ed + }); + }); + }); + + describe("removeLoader", function() { + it("emptys the pagination div when the stream is fetched", function() { + $("#jasmine_content").append($('
OMG
')); + expect($("#paginate").text()).toBe("OMG"); + this.view.photos.trigger("fetched"); + expect($("#paginate")).toBeEmpty(); + }); + }); + +});