Meteor.publish("conversations", function (options) {
    this.unblock();
    var currentUser;

    if(!this.userId){
        return this.ready();
    }

    options = options || {};

    options = _.pick(options, ["limit", "skip"]);

    options.sort = {date:-1};

    Meteor.publishWithRelations({
        handle: this,
        collection: Meteor.participants,
        options:options,
        filter: {userId:this.userId, deleted:{$exists:false}},
        mappings: [{
            key: 'conversationId',
            collection: Meteor.conversations,
            mappings:[{
                key: "conversationId",
                collection: Meteor.participants,
                filter: {userId:{$ne:this.userId}},
                reverse:true,
                mappings:[{
                    key:"userId",
                    collection:Meteor.users,
                    options:{fields:{username:true, avatarId:true}}
                }]
            },{
                reverse:true,
                key: "conversationId",
                collection: Meteor.messages,
                options:{limit:1, sort:{date:-1}}
            }]
        }]
    });

});

/**
 * Publish conversations that have not been read yet by the user
 */
Meteor.publish("unreadConversations", function(){

    if(!this.userId){
        return this.ready();
    }

    Meteor.publishWithRelations({
        handle: this,
        collection: Meteor.participants,
        filter: {userId:this.userId, deleted:{$exists:false}, read:false},
        mappings: [{
            key: 'conversationId',
            collection: Meteor.conversations,
            mappings:[{
                key: "conversationId",
                collection: Meteor.participants,
                reverse:true,
                options:{limit:1, sort:{date:-1}},
                mappings:[{
                    key:"userId",
                    collection:Meteor.users,
                    options:{fields:{username:true, avatarId:true}}
                }]
            },{
                key: "conversationId",
                collection: Meteor.messages,
                options:{limit:1, sort:{date:-1}}
            }]
        }]
    });
});


/**
 * Publish messages for a particular conversation
 * @param   {String}       conversationId The _id of the conversation to fetch messages for
 * @param   {Object}       options        Query options {limit:Number, skip:Number}
 * @returns {Mongo.Cursor} A cursor of messsages that belong to the current conversation
 */
Meteor.publish("messagesFor", function(conversationId, options){
    var self = this;
    var user = User.createEmpty(self.userId);

    self.unblock();

    if(!self.userId){
        return this.ready();
    }

    var conversation = Conversation.createEmpty(conversationId);

    if(user.isParticipatingIn(conversation)){
        return conversation.messages(options.limit, options.skip, "date", -1);
    }
});


/**
 * This publication when subscribed to, updates the state of the participant
 * to keep track of the last message read by the user and whether they are viewing
 * it at this current moment. When the publication stops it updates the participant
 * to indicate they are no longer viewing the conversation
 *
 * @param   {String}       conversationId The _id of the conversation the user is viewing
 */
Meteor.publish("viewingConversation", function(conversationId){
    this.unblock();

    if(!this.userId){
        return this.ready();
    }

    var self = this;
    var sessionId = this._session.id;


    ParticipantsCollection.update({
        conversationId:conversationId, userId:self.userId
    },{
        $addToSet:{observing:sessionId},
        $set:{read:true},
    });

    self.onStop(function () {
        ParticipantsCollection.update({conversationId:conversationId, userId:self.userId}, {$pull:{observing:sessionId}});
    });

    this.ready();
});


/**
 * This publication when subscribed to sets the typing state of a participant in a conversation to true. When stopped it sets it to false.
 * @param   {String}   conversationId The _id of the conversation
 */
Meteor.publish("typing", function(conversationId){
    this.unblock();

    if(!this.userId){
        return this.ready();
    }

    var self = this;

    ParticipantsCollection.update({
        conversationId:conversationId, userId:self.userId
    },{
        $set:{typing:true}
    });

    self.onStop(function () {
        ParticipantsCollection.update({conversationId:conversationId, userId:self.userId}, {$set:{typing:false}});
    });

    this.ready();
});

@@ -1,2935 +1,4 @@
-Meteor.publish(%22conversations%22, function (options) %7B%0A    this.unblock();%0A    var currentUser;%0A%0A    if(!this.userId)%7B%0A        return this.ready();%0A    %7D%0A%0A    options = options %7C%7C %7B%7D;%0A%0A    options = _.pick(options, %5B%22limit%22, %22skip%22%5D);%0A%0A    options.sort = %7Bdate:-1%7D;%0A%0A    Meteor.publishWithRelations(%7B%0A        handle: this,%0A        collection: Meteor.participants,%0A        options:options,%0A        filter: %7BuserId:this.userId, deleted:%7B$exists:false%7D%7D,%0A        mappings: %5B%7B%0A            key: 'conversationId',%0A            collection: Meteor.conversations,%0A            mappings:%5B%7B%0A                key: %22conversationId%22,%0A                collection: Meteor.participants,%0A                filter: %7BuserId:%7B$ne:this.userId%7D%7D,%0A                reverse:true,%0A                mappings:%5B%7B%0A                    key:%22userId%22,%0A                    collection:Meteor.users,%0A                    options:%7Bfields:%7Busername:true, avatarId:true%7D%7D%0A                %7D%5D%0A            %7D,%7B%0A                reverse:true,%0A                key: %22conversationId%22,%0A                collection: Meteor.messages,%0A                options:%7Blimit:1, sort:%7Bdate:-1%7D%7D%0A            %7D%5D%0A        %7D%5D%0A    %7D);%0A%0A%7D);%0A%0A/**%0A * Publish conversations that have not been read yet by the user%0A */%0AMeteor.publish(%22unreadConversations%22, function()%7B%0A%0A    if(!this.userId)%7B%0A        return this.ready();%0A    %7D%0A%0A    Meteor.publishWithRelations(%7B%0A        handle: this,%0A        collection: Meteor.participants,%0A        filter: %7BuserId:this.userId, deleted:%7B$exists:false%7D, read:false%7D,%0A        mappings: %5B%7B%0A            key: 'conversationId',%0A            collection: Meteor.conversations,%0A            mappings:%5B%7B%0A                key: %22conversationId%22,%0A                collection: Meteor.participants,%0A                reverse:true,%0A                options:%7Blimit:1, sort:%7Bdate:-1%7D%7D,%0A                mappings:%5B%7B%0A                    key:%22userId%22,%0A                    collection:Meteor.users,%0A                    options:%7Bfields:%7Busername:true, avatarId:true%7D%7D%0A                %7D%5D%0A            %7D,%7B%0A                key: %22conversationId%22,%0A                collection: Meteor.messages,%0A                options:%7Blimit:1, sort:%7Bdate:-1%7D%7D%0A            %7D%5D%0A        %7D%5D%0A    %7D);%0A%7D);%0A%0A%0A/**%0A * Publish messages for a particular conversation%0A * @param   %7BString%7D       conversationId The _id of the conversation to fetch messages for%0A * @param   %7BObject%7D       options        Query options %7Blimit:Number, skip:Number%7D%0A * @returns %7BMongo.Cursor%7D A cursor of messsages that belong to the current conversation%0A */%0AMeteor.publish(%22messagesFor%22, function(conversationId, options)%7B%0A    var self = this;%0A    var user = User.createEmpty(self.userId);%0A%0A    self.unblock();%0A%0A    if(!self.userId)%7B%0A        return this.ready();%0A    %7D%0A%0A    var conversation = Conversation.createEmpty(conversationId);%0A%0A    if(user.isParticipatingIn(conversation))%7B%0A        return conversation.messages(options.limit, options.skip, %22date%22, -1);%0A    %7D%0A%7D);%0A%0A%0A
 /**%0A

remove data publications