Commit 0b6875f9 authored by Nick Poulden's avatar Nick Poulden
Browse files

New comment system

parent b7e3d3db
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -19,8 +19,10 @@ Ext.define('Docs.Application', {
    ],

    controllers: [
        'Auth',
        'Welcome',
        'Classes',
        'Comments',
        'Search',
        'InlineExamples',
        'Examples',
@@ -46,5 +48,4 @@ Ext.define('Docs.Application', {
        //     Ext.DomQuery.select('link')[4].href = "resources/css/viewport.css?" + Math.ceil(Math.random() * 100000000)
        // }, 1000);
    }

});
+190 −0
Original line number Diff line number Diff line
Ext.define('Docs.controller.Auth', {
    extend: 'Ext.app.Controller',

    requires: ['Ext.util.Cookies'],

    authServer: 'http://projects.sencha.com/auth',

    refs: [
         {
            ref: 'auth',
            selector: 'authentication'
        }
    ],

    init: function() {

        this.sid = Ext.util.Cookies.get('sid');

        this.addEvents(
            /**
             * @event loggedIn
             * Fired after user logs in
             */
            "loggedIn",

            /**
             * @event loggedOut
             * Fired after user logs out
             */
            "loggedOut"
        );

        this.control({
            'authentication': {
                afterrender: function(cmp) {
                    cmp.el.addListener('click', function(e, el) {
                        cmp.showLogin();
                    }, this, {
                        preventDefault: true,
                        delegate: '.login'
                    });

                    cmp.el.addListener('click', function(e, el) {
                        this.logout();
                    }, this, {
                        preventDefault: true,
                        delegate: '.logout'
                    });

                    this.getSession();
                },
                login: this.login
            }
        });
    },

    /**
     * Checks if a user is logged in server side and sets up a local session if they are.
     */
    getSession: function() {

        // if (window.XDomainRequest) {
        //     xdr = new XDomainRequest();
        //     if (xdr) {
        //         xdr.onerror = function() { alert("XDR onerror"); };
        //         xdr.ontimeout =  function() { alert("XDR timeout"); };
        //         xdr.onload =  function() { alert("XDR load " + xdr.responseText); };
        //
        //         xdr.timeout = 2000;
        //         xdr.open('POST', 'http://projects.sencha.com/auth/login');
        //         xdr.send('username=hmm&password=hmm');
        //     } else {
        //         alert('Failed to create');
        //     }
        // }

        Ext.Ajax.request({
            url: this.authServer + '/session',
            params: { sid: this.sid },
            method: 'GET',
            cors: true,
            callback: function(options, success, response) {
                if (success) {
                    this.currentUser = JSON.parse(response.responseText);

                    if (this.currentUser) {
                        this.loggedIn();
                    } else {
                        this.getAuth().showLoggedOut();
                    }
                }
            },
            scope: this
        });
    },

    /**
     * Authenticates a user
     * @param {String} username
     * @param {String} password
     * @param {Boolean} remember
     * @param {Ext.Element} submitEl
     */
    login: function(username, password, remember, submitEl) {

        Ext.Ajax.request({
            url: this.authServer + '/login',
            method: 'POST',
            cors: true,
            params: {
                username: username,
                password: password
            },
            callback: function(options, success, response) {
                var data = JSON.parse(response.responseText);
                if (data.success) {
                    this.currentUser = data;
                    this.setSid(data.sessionID, { remember: remember });
                    this.loggedIn();
                } else {
                    if (this.errorTip) {
                        this.errorTip.update(data.reason);
                        this.errorTip.setTarget(submitEl);
                        this.errorTip.show();
                    } else {
                        this.errorTip = Ext.create('Ext.tip.ToolTip', {
                            anchor: 'bottom',
                            target: submitEl,
                            html: data.reason
                        });
                        this.errorTip.show();
                    }
                }
            },
            scope: this
        });
    },

    /**
     * Logs out a user
     */
    logout: function() {
        Ext.Ajax.request({
            url: this.authServer + '/logout?sid=' + this.sid,
            method: 'POST',
            cors: true,
            callback: function(){
                this.loggedOut();
            },
            scope: this
        });
    },

    /**
     * Marks the user as logged in.
     */
    loggedIn: function() {
        if (this.currentUser) {
            this.getAuth().showLoggedIn(this.currentUser.userName);
            this.fireEvent('loggedIn');
        }
    },

    /**
     * Marks a user as logged out.
     */
    loggedOut: function(user) {
        this.currentUser = {};
        this.setSid(null)
        this.getAuth().showLoggedOut();
        this.fireEvent('loggedOut');
    },

    /**
     * Checks if a user is logged in.
     * @return {Boolean} true if the user is logged in
     */
    isLoggedIn: function() {
        return Boolean(this.sid);
    },

    setSid: function(sid, opts) {
        this.sid = sid;
        if (sid && opts && opts.remember) {
            Ext.util.Cookies.set('sid', sid);
        } else {
            Ext.util.Cookies.clear('sid');
        }
    }
});
+14 −1
Original line number Diff line number Diff line
@@ -54,12 +54,20 @@ Ext.define('Docs.controller.Classes', {

    init: function() {
        this.addEvents(
            /**
             * @event showIndex  Fired after index is shown.
             */
            "showIndex",

            /**
             * @event showClass
             * Fired after class shown. Used for analytics event tracking.
             * @param {String} cls  name of the class.
             * @param {Object} options
             * @param {Boolean} options.reRendered true if the class was re-rendered
             */
            "showClass",

            /**
             * @event showMember
             * Fired after class member scrolled to view. Used for analytics event tracking.
@@ -207,6 +215,7 @@ Ext.define('Docs.controller.Classes', {
    loadIndex: function(noHistory) {
        Ext.getCmp('treecontainer').showTree('classtree');
        this.callParent(arguments);
        this.fireEvent('showIndex');
    },

    /**
@@ -251,6 +260,9 @@ Ext.define('Docs.controller.Classes', {
    },

    showClass: function(cls, anchor) {

        var reRendered = false;

        if (cls === "in-progress") {
            return;
        }
@@ -261,6 +273,7 @@ Ext.define('Docs.controller.Classes', {
            this.getHeader().load(cls);
            this.getOverview().load(cls);
            this.applyExpanded(cls);
            reRendered = true;
        }
        this.currentCls = cls;

@@ -273,7 +286,7 @@ Ext.define('Docs.controller.Classes', {
        }

        this.getTree().selectUrl("#!/api/"+cls.name);
        this.fireEvent('showClass', cls.name);
        this.fireEvent('showClass', cls.name, {reRendered: reRendered});
    },

    scrollContent: function() {
+369 −0
Original line number Diff line number Diff line
Ext.define('Docs.controller.Comments', {
    extend: 'Ext.app.Controller',

    baseUrl: 'http://projects.sencha.com/auth',

    refs: [
        {
            ref: 'toolbar',
            selector: 'classoverview toolbar'
        },
        {
            ref: 'authentication',
            selector: 'authentication'
        },
        {
            ref: 'overview',
            selector: 'classoverview'
        }
    ],

    init: function() {

        this.getController('Classes').on({
            showClass: function(cls, opts) {
                if (opts.reRendered) {
                    Docs.view.Comments.renderCommentMeta();
                }
            },
            showIndex: function() {
                if (!this.commentMetaTotals) {
                    this.renderMetaToClassIndex = true;
                } else if (!this.renderedMetaToClassIndex) {
                    this.updateClassIndex();
                }
            },
            scope: this
        });

        this.getController('Auth').addListener('loggedIn', function() {
            this.loggedIn = true;
            Docs.view.Comments.renderNewCommentForms();
        }, this);

        this.getController('Auth').addListener('loggedOut', function() {
            this.loggedIn = false;
            Docs.view.Comments.renderNewCommentForms();
        }, this);

        this.control({
            'classoverview': {
                afterrender: function(cmp) {
                    // Map user interactions to methods
                    Ext.Array.each([
                        [ '.expandComments',   'click', this.toggleComments],
                        [ '.newCommentSubmit', 'click', this.postComment],
                        [ '.up',               'click', this.voteUp],
                        [ '.down',             'click', this.voteDown],
                        [ '.member-comments',  'click', this.showMemberComments],
                        [ '.del',              'click', this.promptDeleteComment],
                        [ '.new-comment-a',    'click', this.toggleNewComment],
                        [ '.commentGuide',     'click', this.toggleCommentGuide]
                    ], function(delegate) {
                        cmp.el.addListener(delegate[1], delegate[2], this, {
                            preventDefault: true,
                            delegate: delegate[0]
                        });
                    }, this);
                }
            },

            'classoverview toolbar': {
                afterrender: function(cmp) {
                    cmp.el.addListener('click', function() {
                        var commentsDiv = Ext.get(Ext.query('#m-comment .comments')[0]);
                        this.getOverview().scrollToEl('#m-comment', -20);
                        this.openComments(commentsDiv);
                    }, this, {
                        delegate: '.comment-btn'
                    });
                }
            }
        });

        this.fetchCommentMeta();
    },

    fetchCommentMeta: function() {

        Ext.data.JsonP.request({
            url: this.baseUrl + '/comments/_design/Comments/_view/by_target',
            method: 'GET',
            params: {
                reduce: true,
                group_level: 3,
            },
            success: function(response) {

                this.commentMeta = {};
                this.commentMetaTotals = {};
                this.commentIdMap = {};

                Ext.Array.each(response.rows, function(r) {
                    this.updateMeta(r.key, r.value.num);
                }, this);

                if (this.renderMetaToClassIndex) {
                    this.updateClassIndex();
                }
            },
            scope: this
        });
    },

    fetchComments: function(id, callback) {

        var startkey = JSON.stringify(this.commentId(id)),
            endkey = JSON.stringify(this.commentId(id).concat([{}])),
            currentUser = this.getController('Auth').currentUser;

        Ext.data.JsonP.request({
            url: this.baseUrl + '/comments/_design/Comments/_list/with_vote/by_target',
            method: 'GET',
            params: {
                reduce: false,
                startkey: startkey,
                endkey: endkey,
                user: currentUser && currentUser.userName
            },
            success: function(response) {
                callback.call(this, response.rows, id);
            },
            scope: this
        });
    },

    postComment: function(cmp, el) {

        if (!this.getController('Auth').isLoggedIn()) {
            return false;
        }

        var comments = Ext.get(el).up('.comments'),
            id = comments.getAttribute('id'),
            comment = comments.down('textarea').getValue(),
            target = JSON.stringify(this.commentId(id));

        Ext.Ajax.request({
            url: this.addSid(this.baseUrl + '/comments'),
            method: 'POST',
            cors: true,
            params: {
                target: target,
                comment: comment
            },
            callback: function(options, success, response) {
                if (success) {
                    this.updateMeta(this.commentId(id), 1);
                    comments.down('textarea').dom.value = '';
                    this.toggleNewComment(null, el);
                }
                this.fetchComments(id, this.appendNewComment);
            },
            scope: this
        });
    },

    promptDeleteComment: function(cmp, el) {

        if (!this.getController('Auth').isLoggedIn()) {
            return false;
        }

        Ext.Msg.show({
             title:'Are you sure?',
             msg: 'Are you sure you wish to delete this comment?',
             buttons: Ext.Msg.YESNO,
             icon: Ext.Msg.QUESTION,
             fn: function(buttonId) {
                 if (buttonId == 'yes') {
                     this.deleteComment(cmp, el);
                 }
             },
             scope: this
        });
    },

    deleteComment: function(cmp, el) {

        var id = Ext.get(el).up('.comment').getAttribute('id'),
            cls = Ext.get(el).up('.comments').getAttribute('id');

        this.updateMeta(this.commentId(cls), -1);

        Ext.Ajax.request({
            url: this.addSid(this.baseUrl + '/comments/' + id + '/delete'),
            cors: true,
            method: 'POST',
            callback: function(options, success, response) {
                Ext.get(id).remove();
            },
            scope: this
        });
    },

    voteUp: function(cmp, el) {
        this.vote('up', el);
    },

    voteDown: function(cmp, el) {
        this.vote('down', el);
    },

    vote: function(direction, el) {

        if (!this.getController('Auth').isLoggedIn() || Ext.get(el).hasCls('selected')) {
            return false;
        }

        var id = Ext.get(el).up('.comment').getAttribute('id'),
            meta = Ext.get(el).up('.com-meta'),
            scoreEl = meta.down('.score');

        Ext.Ajax.request({
            url: this.addSid(this.baseUrl + '/comments/' + id),
            cors: true,
            method: 'POST',
            params: { vote: direction },
            callback: function(options, success, response) {
                var data = JSON.parse(response.responseText);

                Ext.Array.each(meta.query('.vote a'), function(voteEl) {
                    Ext.get(voteEl).removeCls('selected');
                });
                if (data.direction === 'up' || data.direction === 'down') {
                    Ext.get(meta.query('.vote a.' + data.direction)[0]).addCls('selected');
                }
                scoreEl.update(String(data.total));
            },
            scope: this
        });
    },

    toggleComments: function(cmp, el) {

        var commentsDiv = Ext.get(el).up('.comments');

        if (commentsDiv.hasCls('open')) {
            this.closeComments(commentsDiv);
        } else {
            this.openComments(commentsDiv);
        }
    },

    openComments: function(commentsDiv) {

        if (commentsDiv.hasCls('open')) return;

        var commentNum =  commentsDiv.down('.name'),
            commentsList =  commentsDiv.down('.comment-list');

        commentsDiv.addCls('open');
        commentNum.setStyle('display', 'none');
        if (commentsList) {
            commentsList.setStyle('display', 'block');
        } else {
            var id = commentsDiv.getAttribute('id');
            this.fetchComments(id, this.renderComments);
        }
    },

    closeComments: function(commentsDiv) {

        if (!commentsDiv.hasCls('open')) return;

        var commentNum =  commentsDiv.down('.name'),
            commentsList =  commentsDiv.down('.comment-list');

        commentsDiv.removeCls('open');
        commentNum.setStyle('display', 'block');
        if (commentsList) {
            commentsList.setStyle('display', 'none');
        }
    },

    showMemberComments: function(cml, el) {
        var member = Ext.get(el).up('.member'),
            commentsDiv = member.down('.comments');

        member.addCls('open');
        this.openComments(commentsDiv);
        this.getOverview().scrollToEl(commentsDiv, -20);
    },

    renderComments: function(rows, id) {
        var comments = Ext.get(id);
        var data = Ext.Array.map(rows, function(r) {
            r.value.id = r.id;
            return r.value;
        });
        Docs.view.Comments.commentsTpl.append(comments, data);

        var commentTpl = (this.loggedIn ? Docs.view.Comments.loggedInCommentTpl : Docs.view.Comments.loggedOutCommentTpl);
        commentTpl.overwrite(comments.down('.new-comment-wrap'), this.loggedIn ? this.getController('Auth').currentUser : {});
    },

    toggleNewComment: function(cmp, el) {
        if (!this.loggedIn) {
            this.getAuthentication().showLogin();
            return;
        }

        var newCommentEl = Ext.get(el).up('.new-comment');
        if (newCommentEl.hasCls('open')) {
            newCommentEl.removeCls('open');
        } else {
            newCommentEl.addCls('open');
        }
    },

    appendNewComment: function(rows, id) {
        var newCommentWrap = Ext.get(id).down('.new-comment-wrap'),
            data = rows[rows.length - 1].value;

        data.id = rows[rows.length - 1].id;
        Docs.view.Comments.commentTpl.insertBefore(newCommentWrap, data);
    },

    addSid: function(url) {
        var sid = this.getController('Auth').sid;
        return url + (url.match(/\?/) ? '&' : '?') + 'sid=' + sid;
    },

    updateClassIndex: function(meta) {

        for(var c in this.commentMetaTotals) {
            var cls = Ext.query('#classindex a[rel="' + c + '"]');
            if (cls && cls[0]) {
                Docs.view.Comments.memberCommentsTpl.append(cls[0], [String(this.commentMetaTotals[c])]);
            }
        }

        this.renderedMetaToClassIndex = true;
    },

    updateMeta: function(key, value) {
        var k = (key[2] === '') ? key[1] : key.slice(1,3).join('-'),
            c = key[1],
            divId = 'comments-class-' + k.replace(/\./g, '-');

        this.commentMeta[k] = this.commentMeta[k] || 0;
        this.commentMeta[k] += value;
        this.commentMetaTotals[c] = this.commentMetaTotals[c] || 0;
        this.commentMetaTotals[c] += value;

        this.commentIdMap[divId] = key;
    },

    commentId: function(id) {
        return this.commentIdMap[id] || ['unknown'];
    },

    toggleCommentGuide: function(e, el) {
        var commentForm = Ext.get(el).up('.newCommentForm'),
            guideText = commentForm.down('.commentGuideTxt'),
            curDisplay = guideText.getStyle('display');

        guideText.setStyle('display', (curDisplay == 'none') ? 'block' : 'none');
    }
});
 No newline at end of file
+204 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading