%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /proc/985914/root/data/old/home/stash/atlassian-stash/static/feature/comments/
Upload File :
Create Path :
Current File : //proc/985914/root/data/old/home/stash/atlassian-stash/static/feature/comments/comment-container.js

define('feature/comments/comment-container', [
    'jquery',
    'memoir',
    'underscore',
    'util/deprecation',
    'util/dom-event',
    'util/events',
    'util/scroll',
    'model/page-state',
    'widget/aui/form',
    'widget/confirm-dialog',
    'widget/markup-preview',
    'feature/comments/comment-collection',
    'feature/comments/comment-model',
    'feature/comments/comment-tips'
], function (
    $,
    memoir,
    _,
    deprecate,
    domEventUtil,
    events,
    scrollUtil,
    pageState,
    form,
    ConfirmDialog,
    markupPreview,
    CommentCollection,
    Comment,
    commentTips
) {

    "use strict";

    /**
     * Backbone view. Requires:
     * * this.anchor || options.anchor,
     * * this.rootCommentListSelector || options.rootCommentListSelector
     */

    var COMMENT_CONTAINER_MIN_WIDTH = 450; // Minimum width for showing comment tips in comment form

    return Backbone.View.extend({
        initialize : function() {
            _.bindAll(this, 'onMarkupPreviewChanged');

            this.anchor = this.anchor || this.options.anchor;
            this.rootCommentListSelector = this.rootCommentListSelector || this.options.rootCommentListSelector;
            this.context = this.options.context;
            this.pullRequest = this.options.pullRequest || pageState.getPullRequest();

            if (!this.collection) {
                this.collection = new CommentCollection([], {
                    anchor : this.anchor
                });
            }

            this.initDeleteButtons();
            this.$el.imagesLoaded(this.onImagesLoaded.bind(this));
            deprecate.triggerDeprecated('stash.feature.comments.comment-container.added', this.$el, 'stash.feature.comments.commentContainerAdded', '2.11', '3.0');
            events.trigger('stash.feature.comments.commentContainerAdded', null, this.$el);

            // We want to debounce for each instance of CommentContainer independently
            // so we have to bind the debounce at instantiation time
            var draftDebounceWait = 300;

            this.updateDraftComment = _.debounce(this.updateDraftComment, draftDebounceWait);
            //Make sure that draft deletion is always performed after any pending updates
            this.deleteDraftComment = _.debounce(this.deleteDraftComment, draftDebounceWait);
        },
        events : {
            'submit form' : 'onFormSubmit',
            'click a.times' : 'onDateClicked',
            'click .cancel' : 'onCancelClicked',
            'click .reply' : 'onReplyClicked',
            'click .edit' : 'onEditClicked',
            'keydown textarea' : 'onTextareaKeydown'
        },
        initDeleteButtons : function(e) {
            this.createDeleteDialog().attachTo('.delete', null, this.el);
        },
        createDeleteDialog : function() {
            var self = this;
            var confirmDialog = new ConfirmDialog({
                id : "delete-repository-dialog",
                titleText: AJS.I18n.getText('stash.web.comment.delete.title'),
                titleClass : 'warning-header',
                panelContent : "<p>" + AJS.I18n.getText('stash.web.comment.delete.confirm') + "</p>",
                submitText : AJS.I18n.getText('stash.web.button.delete'),
                submitToHref : false
            });

            confirmDialog.addConfirmListener(function(promise, $trigger, removeDialog) {
                removeDialog();
                self.deleteComment($trigger.closest('.comment'));
            });
            return confirmDialog;
        },
        onFormSubmit : function(e) {
            e.preventDefault();
            // Comment containers can be nested (e.g. diff comment container inside activity-comment-container)
            // so we stopPropagation so it is only handled by the inner most container
            e.stopPropagation();
            this.submitCommentForm($(e.target));
        },
        onDateClicked : function(e) {
            e.preventDefault();
            // Comment containers can be nested (e.g. diff comment container inside activity-comment-container)
            // so we stopPropagation so it is only handled by the inner most container
            e.stopPropagation();
            $('.comment.focused').removeClass('focused');
            var $a = $(e.target).closest('a');
            $a.closest('.comment').addClass('focused');
            memoir.pushState(null, null, $a.prop('href'));
        },
        onCancelClicked : function(e) {
            e.preventDefault();
            // Comment containers can be nested (e.g. diff comment container inside activity-comment-container)
            // so we stopPropagation so it is only handled by the inner most container
            e.stopPropagation();
            this.cancelCommentForm($(e.target).closest('form'));
        },
        onReplyClicked : function(e) {
            e.preventDefault();
            // Comment containers can be nested (e.g. diff comment container inside activity-comment-container)
            // so we stopPropagation so it is only handled by the inner most container
            e.stopPropagation();
            this.openReplyForm($(e.target).closest('.comment'));
        },
        onEditClicked : function(e) {
            e.preventDefault();
            // Comment containers can be nested (e.g. diff comment container inside activity-comment-container)
            // so we stopPropagation so it is only handled by the inner most container
            e.stopPropagation();
            this.openEditForm($(e.target).closest('.comment'));
        },
        onImagesLoaded : function($img) {
            // check if $img is in the DOM, if it is then we should fire a change event for codemirror
            if ($.contains(document.documentElement, $img[0])) {
                this.trigger('change');
            }
        },
        onTextareaKeydown : function(e) {
            if (domEventUtil.isCtrlish(e) && e.which === $.ui.keyCode.ENTER) { // Handle Ctrl+Enter/Cmd+Enter
                e.preventDefault();
                // Comment containers can be nested (e.g. diff comment container inside activity-comment-container)
                // so we stopPropagation so it is only handled by the inner most container
                e.stopPropagation();
                $(e.target).closest('form').submit();
            } else if ($(e.target).closest('.comment-container').is(this.el)) { // Handle nested comment containers
                // Unfortunately if you've written a draft, then focus in another comment form (e.g. general comment form) and press a keyboard shortcut (such as cmd+r)
                // it will treat it as a change, even though you haven't really typed anything, and it will overwrite your other draft. Bit of an edge case though.
                this.updateDraftComment(e.target);
            }
        },
        updateDraftComment : function(textArea) {
            var draft = this.getDraftCommentFromForm($(textArea).closest('form'));
            this.context && this.context.saveDraftComment(draft);
        },
        getDraftCommentFromForm: function($form){
            var draft = this.getCommentFormJSON($form);

            if (draft.anchor) {
                //commitRange adds a bunch of noise to the stored comment
                delete draft.anchor.commitRange;
            }

            // $.extend doesn't copy undefined properties. JSON.stringify throws them away, so this gives us a consistent object for equality checks,
            // regardless of whether it's retrieved from the form or from sessionStorage.
            return $.extend({}, draft);
        },
        deleteDraftComment : function(draft) {
            this.context && this.context.deleteDraftComment(draft);
        },
        getRootCommentList : function() {
            var $list = this.$(this.rootCommentListSelector);
            if (!$list.length) {
                $list = this.$el;
            }
            return $list;
        },
        render : function() {
            var $newEl = stash.feature.comments($.extend({
                comments : this.collection.toJSON()
            }, this.anchor.toJSON()));

            this.$el.replaceWith($newEl);

            this.setElement($newEl[0]);
        },
        _toJSON : function($comment, text) {
            var parentId = parseInt($comment.parent().closest('.comment').attr('data-id'), 10);
            var id = parseInt($comment.attr('data-id'), 10);
            var version = parseInt($comment.attr('data-version'), 10);
            return {
                id : !isNaN(id) ? id : undefined,
                version : !isNaN(version) ? version : undefined,
                text : text,
                anchor : this.anchor.toJSON(),
                parent : !isNaN(parentId) ? { id : parentId } : undefined
            };

        },
        getCommentJSON : function($comment) {
            return this._toJSON($comment, $comment.find('> .content > .message').attr('data-text')); // make sure to use .attr here to avoid type conversion by jQuery
        },
        getCommentFormJSON : function($form) {
            var $commentRoot = $form.parent().is('.comment') ? $form.parent() : $form;
            return this._toJSON($commentRoot, $form.find('textarea').val());
        },
        enableDeletion : function($comment) {
            $comment.find('> .content > .actions > li > .delete').parent().removeClass('hidden');
        },
        disableDeletion : function($comment) {
            $comment.find('> .content > .actions > li > .delete').parent().addClass('hidden');
        },
        renderContentUpdate : function($comment, commentJSON) {
            $comment.children('.content').replaceWith(stash.feature.commentContent({
                pullRequest: this.pullRequest && this.pullRequest.toJSON(),
                comment: commentJSON,
                hideDelete: !!$comment.find('> .replies > .comment').length
            }));
            $comment.attr('data-version', commentJSON.version).data('version', commentJSON.version);

            this.$el.imagesLoaded(this.onImagesLoaded.bind(this));
            this.trigger('change');
            deprecate.triggerDeprecated('stash.feature.comments.comment.edited', $comment, commentJSON, this.context, 'stash.feature.comments.commentEdited', '2.11', '3.0');
            events.trigger('stash.feature.comments.commentEdited', null, commentJSON, $comment);
        },
        insertCommentIntoList : function($comment, $commentList, commentJSON) {
            var $insertBefore = $commentList.children('.comment:first');
            //TODO HACK: sort by ID is implied sort by createdDate. is that even what we want?
            while ($insertBefore.length) {
                if (parseInt($insertBefore.data('id'), 10) > commentJSON.id) {
                    break;
                }
                $insertBefore = $insertBefore.next('.comment');
            }

            $insertBefore = $insertBefore.length ?
                                $insertBefore :
                                $commentList.children('.comment-form-container:last');

            if ($insertBefore.length) {
                $comment.insertBefore($insertBefore);
            } else {
                $commentList.append($comment);
            }
        },
        renderComment : function(commentJSON, parentId, isContentUpdate) {

            var $comment;

            if (isContentUpdate && ($comment = $('.comment[data-id="' + commentJSON.id + '"]')).length) {
                return this.renderContentUpdate($comment, commentJSON);
            }

            commentJSON = $.extend({
                isNew : true
            }, commentJSON);

            var $parent = parentId && this.$('[data-id=' + parentId + ']');

            if ($parent) { // disable deleting the parent now that it has replies
                this.disableDeletion($parent);
            }

            var $insertUnder = ($parent ? // a reply
                 $parent.children('.replies') :
                 this.getRootCommentList());

            $comment = $(stash.feature.comment({
                pullRequest : this.pullRequest && this.pullRequest.toJSON(),
                numOfAncestors: $insertUnder.parents('.comment').length,
                extraClasses: this.getExtraCommentClasses(),
                comment: commentJSON
            }));

            this.insertCommentIntoList($comment, $insertUnder, commentJSON);

            // Should match the timing of the target-fade-animation in comments.less
            // We remove the clsas because Chrome bugs out and replays the animation when switching between
            // fixed and normal modes on the diff page.
            var targetFadeAnimationTime = 5000;
            setTimeout(_.bind($comment.removeClass, $comment, 'new'), targetFadeAnimationTime);

            scrollUtil.scrollTo($comment);
            $comment.hide().fadeIn('slow');

            this.$el.imagesLoaded(this.onImagesLoaded.bind(this));
            this.trigger('change');
            deprecate.triggerDeprecated('stash.feature.comments.comment.added', $comment, commentJSON, this.context, 'stash.feature.comments.commentAdded', '2.11', '3.0');
            events.trigger('stash.feature.comments.commentAdded', null, commentJSON, $comment);
        },
        getExtraCommentClasses : function() {
            return '';  // Can be overridden for different contexts
        },
        showErrorMessage : function(model, error) {
            var $form = this;
            var $error = $form.find('.error');
            if (!$error.length) {
                $error = $('<div class="error"></div>');
                $form.find('.comment-form-footer').before($error);
            }
            $error.text(error);
        },
        cancelCommentForm : function($form) {
            this.closeCommentForm($form);
        },
        submitCommentForm : function($form) {
            if (form.isSubmissionPrevented($form)) {
                return;
            }

            var self = this;
            var $spinner = $form.find('.comment-submit-spinner');

            var commentJSON = this.getCommentFormJSON($form);
            var isEdit = commentJSON.id != null;
            var isKnown = isEdit && this.collection.get(commentJSON.id);
            var parentId = commentJSON.parent && commentJSON.parent.id;
            var comment = isKnown ? this.collection.get(commentJSON.id) : new Comment();

            comment.on('invalid', this.showErrorMessage, $form);
            if (comment.set($.extend(commentJSON, { avatarSize: stash.widget.avatarSizeInPx({ size: 'medium' }) }), { validate: true})) {
                if (!isKnown) {
                    this.collection.push(comment);
                }
                // need to override the positioning - spin.js can't handle the absolute positioning of the container.
                $form.addClass('submitting');
                $spinner.spin('medium');
                form.preventSubmission($form);
                comment.save().done(function(commentResp) {
                    // hack to avoid "future" comments
                    commentResp.createdDate = Math.min(commentResp.createdDate, new Date().getTime());
                    commentResp.updatedDate = Math.min(commentResp.updatedDate, new Date().getTime());

                    self.closeCommentForm($form, { doNotDestroy : true });
                    self.renderComment(commentResp, parentId, isEdit);
                    self.trigger('comment.saved');
                }).fail(function() {
                    if (!isKnown && !isEdit) { // it was a totally new comment. remove it from our models.
                        self.collection.remove(commentJSON.id);
                    }
                }).always(function() {
                    $spinner.spinStop();
                    $form.removeClass('submitting');
                    form.allowSubmission($form);
                });
            }
            comment.off('invalid', this.showErrorMessage);
        },
        deleteComment : function($comment) {
            var commentJSON = this.getCommentJSON($comment);
            var comment;
            if (this.collection.get(commentJSON)) {
                comment = this.collection.get(commentJSON.id);
            } else {
                comment = new Comment(commentJSON);
                this.collection.push(comment);
            }
            var $delete = $comment.find('> .content .delete'),
                deleteDims = { h : $delete.height(), w : $delete.width() };
            $delete.height(deleteDims.h).width(deleteDims.w).css('vertical-align', 'middle').empty().spin('small');

            var self = this;
            comment.destroy({ wait: true }).always(function() {
                $delete.spinStop();
            }).done(function() {
                var $parent = $comment.parent().closest('.comment');
                if ($parent.length) {
                    // has siblings if there 1) are sibling comments, or 2) there is a form being edited
                    var hasSiblings = $comment.siblings('.comment').length || $comment.siblings('.comment-form-container').find('textarea').val();
                    if (!hasSiblings) { // this was the only child. reenable deletion on its parent
                        self.enableDeletion($parent);
                    }
                }
                $comment.fadeOut(function () {
                    $comment.remove();
                    self.onCommentDeleted();

                    self.trigger('change');
                    deprecate.triggerDeprecated("stash.feature.comments.comment.deleted", commentJSON, self.context, "stash.feature.comments.commentDeleted", '2.11', '3.0');
                    events.trigger("stash.feature.comments.commentDeleted", null, commentJSON);
                });

            }).fail(function() { // revert to Delete
                $delete.css('height', '').css('width', '').css('vertical-align', '').text(AJS.I18n.getText('stash.web.comment.delete'));
            });
        },
        onCommentDeleted : function() {
            // Can be overridden
        },
        onMarkupPreviewChanged : $.noop,
        /**
         *
         * @param {jQuery} $comment
         * @returns {jQuery} - The comment form
         */
        openEditForm : function($comment) {
            var commentJSON = this.getCommentJSON($comment);
            var $form = $(stash.feature.commentForm($.extend({
                    tips : this.$el.width() > COMMENT_CONTAINER_MIN_WIDTH ? commentTips.tips : [],
                    currentUser: pageState.getCurrentUser() && pageState.getCurrentUser().toJSON()
                }, commentJSON)
            ));
            var $originalContent = $comment.find('> .user-avatar, > .content');
            $originalContent.remove();
            $comment.prepend($form).addClass('comment-form-container');
            $form.data('originalContent', $originalContent);
            $form.find('textarea.expanding').expandingTextarea();
            // IE scrolls the diff pane left to the start of the textarea when calling focus() immediately
            _.defer(function(){ $form.find('textarea').focus(); });

            this.trigger('change');
            deprecate.triggerDeprecated('stash.feature.comments.comment-form.displayed', $form, 'stash.feature.comments.commentFormShown', '2.11', '3.0');
            events.trigger('stash.feature.comments.commentFormShown', null, $form);

            markupPreview.bindTo($form, {
                callback : this.onMarkupPreviewChanged
            });

            return $form;
        },
        /**
         *
         * @param {jQuery} $replyToComment
         * @returns {jQuery} - The comment form
         */
        openReplyForm : function($replyToComment) {
            var $replies = $replyToComment.children('.replies');
            return this.openCommentForm($replies, { location: 'top' });
        },
        /**
         *
         * @returns {jQuery} - The comment form
         */
        openNewCommentForm : function() {
            return this.openCommentForm(this.getRootCommentList(), { location: 'bottom' });
        },
        /**
         *
         * @param {jQuery} $commentList
         * @param {?Object} options
         * @returns {jQuery} - The comment form
         */
        openCommentForm : function($commentList, options) {
            var attachmentMethod = options && options.location === 'top' ? 'prependTo' : 'appendTo';
            // check if there is a form open that is not an edit form.
            var $formContainer = $commentList.children('.comment-form-container').not('.comment');
            if (!$formContainer.length) {
                $formContainer = $(stash.feature.commentFormListItem({
                    tips : this.$el.width() > COMMENT_CONTAINER_MIN_WIDTH ? commentTips.tips : [],
                    currentUser: pageState.getCurrentUser() && pageState.getCurrentUser().toJSON()
                }))[attachmentMethod]($commentList);
            }
            $formContainer.find('textarea.expanding').expandingTextarea();
            // IE scrolls the diff pane left to the start of the textarea when calling focus() immediately
            _.defer(function() {
                $formContainer.find('textarea').focus();
            });
            var $form = $formContainer.find('form');
            markupPreview.bindTo($form, {
                callback : this.onMarkupPreviewChanged
            });

            this.trigger('change');
            deprecate.triggerDeprecated('stash.feature.comments.comment-form.displayed', $form, 'stash.feature.comments.commentFormShown', '2.11', '3.0');
            events.trigger('stash.feature.comments.commentFormShown', null, $form);

            return $form;
        },
        closeCommentForm : function($form) {
            $form.find('.error').remove(); // clear errors
            this.deleteDraftComment(this.getDraftCommentFromForm($form));

            markupPreview.unbind($form);

            var $originalContent = $form.data('originalContent');
            var $li = $form.parent();

            if ($originalContent) { // edits - the form is inside a living comment, so revert back to that comment's content.
                $li.removeClass('comment-form-container');

                $form.replaceWith($originalContent);
            } else { // it's a new comment - remove it entirely.
                $li.remove();
            }

            this.trigger('change');
            deprecate.triggerDeprecated('stash.feature.comments.comment-form.closed', $form, 'stash.feature.comments.commentFormHidden', '2.11', '3.0');
            events.trigger('stash.feature.comments.commentFormHidden', null, $form);
        },
        /**
         * For a given comment form, populate the contents of its textarea with the draft text
         * and set the appropriate attributes which are cleared on the first interaction with the comment
         * @param {jQuery|HTMLElement} form - The comment form
         * @param {Object} draft - The draft to populate from
         */
        populateCommentFormFromDraft: function(form, draft) {
            $(form).find('textarea')
                .val(draft.text)
                .addClass('restored')
                .attr("title",  AJS.I18n.getText('stash.web.comment.restored.draft.title'))
                .trigger('input') //Fake an input to trigger initial sizing of textarea
                .one('click keypress', function(){
                    $(this)
                        .removeClass('restored')
                        .removeAttr("title");
                });
        },
        /**
         * Get the comment element from the DOM with the matching comment ID
         * @param {number} commentId
         * @returns {jQuery}
         */
        getCommentElById: function(commentId){
            return this.$('.comment[data-id=' + commentId + ']');
        },
        /**
         * Attempt to restore a draft comment and return success or failure
         * @param {Object} draft
         * @returns {boolean} - success
         */
        restoreDraftComment: function(draft) {
            var $form;

            if (draft.id) {
                //edit
                var $comment = this.getCommentElById(draft.id);

                if ($comment.length) {
                    if (parseInt(draft.version, 10) < parseInt($comment.attr('data-version'), 10)  ) {
                        //to avoid overwriting an external modification, discard drafts whose version is older than the current version
                        this.context.deleteDraftComment(draft);
                        return true; //even though we didn't restore it, we don't want it to be kept around
                    }

                    $form = this.openEditForm($comment);
                }
            } else if (draft.parent) {
                //reply
                var $parent = this.getCommentElById(draft.parent.id);

                if ($parent.length) {
                    $form = this.openReplyForm($parent);
                }
            } else {
                //new comment
                $form = this.openNewCommentForm();
            }

            $form && this.populateCommentFormFromDraft($form, draft);

            return !!$form;
        },
        destroy : $.noop
    });
});

Zerion Mini Shell 1.0