<commit_before>/*
 * Displays a list of existing comments within a comment dialog.
 *
 * Each comment in the list is an existing, published comment that a user
 * has made. They will be displayed, along with any issue states and
 * identifying information, and links for viewing the comment on the review
 * or replying to it.
 *
 * This is used internally in CommentDialogView.
 */
var CommentsListView = Backbone.View.extend({
    itemTemplate: _.template([
        '<li class="<%= itemClass %>">',
         '
',
          '<%- comment.user.name %>',
          '',
           '<a href="<%= comment.url %>">View',
           '<a href="<%= reviewRequestURL %>',
                    '?reply_id=<%= comment.comment_id %>',
                    '&reply_type=<%= replyType %>">Reply',
          '',
         '
',
         '
<%- comment.text %>
'
    ].join('')),

    /*
     * Set the list of displayed comments.
     */
    setComments: function(comments, replyType) {
        var reviewRequestURL = this.options.reviewRequestURL,
            commentIssueManager = this.options.commentIssueManager,
            odd = true,
            $items = $();

        if (comments.length === 0) {
            return;
        }

        _.each(comments, function(comment) {
            var $item = $(this.itemTemplate({
                comment: comment,
                itemClass: odd ? 'odd' : 'even',
                reviewRequestURL: reviewRequestURL,
                replyType: replyType
            }));

            if (comment.issue_opened) {
                var interactive = window['gEditable'],
                    $issue = $('
').issueIndicator();

                if (interactive) {
                    $issue.issueButtons();
                }

                $issue
                    .commentIssue(comment.review_id, comment.comment_id,
                                  replyType, comment.issue_status,
                                  interactive)
                    .appendTo($item);

                commentIssueManager.registerCallback(comment.comment_id,
                    function(issue_status) {
                        comment.issue_status = issue_status;
                    });
            }

            $items = $items.add($item);

            odd = !odd;
        }, this);

        this.$el
            .empty()
            .append($items);
    }
});

/*
 * A dialog that allows for creating, editing or deleting draft comments on
 * a diff or file attachment. The dialog can be moved around on the page.
 *
 * Any existing comments for the selected region will be displayed alongside
 * the dialog for reference. However, this dialog is not intended to be
 * used to reply to those comments.
 */
RB.CommentDialogView = Backbone.View.extend({
    DIALOG_TOTAL_HEIGHT: 250,
    SLIDE_DISTANCE: 10,
    COMMENTS_BOX_WIDTH: 280,
    FORM_BOX_WIDTH: 380,

    className: 'comment-dlg',
    template: _.template($('#comment-dlg-template').html()),

    events: {
        'click .buttons .cancel': '_onCancelClicked',
        'click .buttons .delete': '_onDeleteClicked',
        'click .buttons .save': '_onSaveClicked',
        'keydown textarea': '_onTextKeyDown',
        'keyup textarea': '_onTextKeyUp'
    },

    initialize: function() {
        this._ignoreKeyUp = false;
    },

    render: function() {
        this.options.animate = (this.options.animate !== false);

        this.$el
            .hide()
            .html(this.template());

        this._$draftForm    = this.$el.find('form');
        this._$commentsPane = this.$el.find('.other-comments');
        this._$buttons      = this._$draftForm.find(".buttons");
        this._$statusField  = this._$draftForm.find(".status");
        this._$issueOptions = this._$draftForm.find(".comment-issue-options");

        this._$issueField = this._$issueOptions.find('input')
            .bindProperty('checked', this.model, 'openIssue')
            .bindProperty('disabled', this.model, 'editing', {
                elementToModel: false,
                inverse: true
            });

        this._$saveButton = this._$buttons.find('input.save')
            .bindProperty('disabled', this.model, 'canSave', {
                elementToModel: false,
                inverse: true
            });

        this._$cancelButton = this._$buttons.find('input.cancel');

        this._$deleteButton = this._$buttons.find('input.delete')
            .bindVisibility(this.model, 'canDelete')
            .bindProperty('disabled', this.model, 'canDelete', {
                elementToModel: false,
                inverse: true
            });

        this._commentsList = new CommentsListView({
            el: this._$commentsPane.find('ul'),
            reviewRequestURL: this.options.reviewRequestURL,
            commentIssueManager: this.options.commentIssueManager
        });

        /*
         * We need to handle keypress here, rather than in events above,
         * because jQuery will actually handle it. Backbone fails to.
         */
        this._$textField = this._$draftForm.find('textarea')
            .keypress(_.bind(this._onTextKeyPress, this))
            .bindProperty('disabled', this.model, 'canEdit', {
                elementToModel: false,
                inverse: true
            })
            .bindProperty('value', this.model, 'text', {
                elementToModel: false
            });

        this.$el
            .css("position", "absolute")
            .mousedown(function(evt) {
                /*
                 * Prevent this from reaching the selection area, which will
                 * swallow the default action for the mouse down.
                 */
                evt.stopPropagation();
            })
            .proxyTouchEvents();

        if (!$.browser.msie || $.browser.version >= 9) {
            /*
             * resizable is pretty broken in IE 6/7.
             */
            var grip = $("")
                .addClass("ui-resizable-handle ui-resizable-grip")
                .attr("src", STATIC_URLS["rb/images/resize-grip.png"])
                .insertAfter(this._$buttons)
                .proxyTouchEvents();

            this.$el.resizable({
                handles: $.browser.mobileSafari ? "grip,se"
                                                : "grip,n,e,s,w,se,sw,ne,nw",
                transparent: true,
                resize: _.bind(this._handleResize, this)
            });

            /* Reset the opacity, which resizable() changes. */
            grip.css("opacity", 100);
        }

        if (!$.browser.msie || $.browser.version >= 7) {
            /*
             * draggable works in IE7 and up, but not IE6.
             */
            this.$el.draggable({
                handle: $(".title", this).css("cursor", "move")
            });
        }

        this.model.on('change:dirty', function() {
            if (this.$el.is(':visible')) {
                this._handleResize();
            }
        }, this);

        this.model.on('change:statusText', function(model, text) {
            this._$statusField.text(text || '');
        }, this);

        this.model.on('change:publishedComments',
                      this._onPublishedCommentsChanged, this);
        this._onPublishedCommentsChanged();

        return this;
    },

    /*
     * Opens the comment dialog and focuses the text field.
     */
    open: function(fromEl) {
        function openDialog() {
            this.$el.scrollIntoView();
            this._$textField.focus();
        }

        this.$el
            .css({
                top: parseInt(this.$el.css("top"), 10) - this.SLIDE_DISTANCE,
                opacity: 0
            })
            .show();

        this._handleResize()

        if (this.model.get('canEdit')) {
            this.model.beginEdit();
        }

        if (this.options.animate) {
            this.$el.animate({
                top: "+=" + this.SLIDE_DISTANCE + "px",
                opacity: 1
            }, 350, "swing", _.bind(openDialog, this));
        } else {
            openDialog.call(this);
        }
    },

    /*
     * Closes the comment dialog, discarding the comment block if empty.
     *
     * This can optionally take a callback and context to notify when the
     * dialog has been closed.
     */
    close: function(onClosed, context) {
        function closeDialog() {
            this.model.close();
            this.$el.remove();
            this.trigger("closed");

            if (_.isFunction(onClosed)) {
                onClosed.call(context);
            };
        }

        if (this.options.animate && this.$el.is(":visible")) {
            this.$el.animate({
                top: "-=" + this.SLIDE_DISTANCE + "px",
                opacity: 0
            }, 350, "swing", _.bind(closeDialog, this));
        } else {
            closeDialog.call(this);
        }
    },

    /*
     * Moves the comment dialog to the given coordinates.
     */
    move: function(x, y) {
        this.$el.move(x, y);
    },

    /*
     * Positions the dialog beside an element.
     *
     * This takes the same arguments that $.fn.positionToSide takes.
     */
    positionBeside: function($el, options) {
        this.$el.positionToSide($el, options);
    },

    /*
     * Callback for when the list of published comments changes.
     *
     * Sets the list of comments in the CommentsList, and factors in some
     * new layout properties.
     */
    _onPublishedCommentsChanged: function() {
        var comments = this.model.get('publishedComments') || [],
            showComments = (comments.length > 0),
            width = this.FORM_BOX_WIDTH;

        this._commentsList.setComments(comments,
                                       this.model.get('publishedCommentsType'));
        this._$commentsPane.setVisible(showComments);

        /* Do this here so that calculations can be done before open() */

        if (showComments) {
            width += this.COMMENTS_BOX_WIDTH;
        }

        /* Don't let the text field bump up the size we set below. */
        this._$textField
            .width(10)
            .height(10);

        this.$el
            .width(width)
            .height(this.DIALOG_TOTAL_HEIGHT);
    },

    /*
     * Handles the resize of the comment dialog. This will lay out the
     * elements in the dialog appropriately.
     */
    _handleResize: function() {
        var $draftForm = this._$draftForm,
            $commentsPane = this._$commentsPane,
            $commentsList = this._commentsList.$el,
            $textField = this._$textField,
            textFieldPos,
            width = this.$el.width(),
            height = this.$el.height(),
            formWidth = width - $draftForm.getExtents("bp", "lr"),
            boxHeight = height,
            commentsWidth = 0;

        if ($commentsPane.is(":visible")) {
            $commentsPane
                .width(this.COMMENTS_BOX_WIDTH -
                       $commentsPane.getExtents("bp", "lr"))
                .height(boxHeight - $commentsPane.getExtents("bp", "tb"))
                .move(0, 0, "absolute");

            $commentsList.height($commentsPane.height() -
                                 $commentsList.position().top -
                                 $commentsList.getExtents("bmp", "b"));

            commentsWidth = $commentsPane.outerWidth(true);
            formWidth -= commentsWidth;
        }

        $draftForm
            .width(formWidth)
            .height(boxHeight - $draftForm.getExtents("bp", "tb"))
            .move(commentsWidth, 0, "absolute");

        textFieldPos = $textField.position();

        $textField
            .width($draftForm.width() - textFieldPos.left -
                   $textField.getExtents("bmp", "r"))
            .height($draftForm.height() - textFieldPos.top -
                    this._$buttons.outerHeight(true) -
                    this._$statusField.height() -
                    this._$issueOptions.height() -
                    $textField.getExtents("bmp", "b"));
    },

    /*
     * Callback for when the Cancel button is pressed.
     *
     * Cancels the comment (which may delete the comment block, if it's new)
     * and closes the dialog.
     */
    _onCancelClicked: function() {
        this.model.cancel();
        this.close();
    },

    /*
     * Callback for when the Delete button is pressed.
     *
     * Deletes the comment and closes the dialog.
     */
    _onDeleteClicked: function() {
        if (this.model.get('canDelete')) {
            this.model.deleteComment();
            this.close();
        }
    },

    /*
     * Callback for when the Save button is pressed.
     *
     * Saves the comment, creating it if it's new, and closes the dialog.
     */
    _onSaveClicked: function() {
        if (this.model.get('canSave')) {
            this.model.save();
            this.close();
        }
    },

    /*
     * Callback for key down events in the text field.
     *
     * If the Escape key is pressed, the dialog will be closed.
     *
     * The keydown event won't be propagated to the parent elements.
     */
    _onTextKeyDown: function(e) {
        e.stopPropagation();

        if (e.which === $.ui.keyCode.ESCAPE) {
            this._onCancelClicked();
            return false;
        }
    },

    /*
     * Callback for key press events in the text field.
     *
     * If the Control-Enter or Alt-I keys are pressed, we'll handle them
     * specially. Control-enter is the same thing as clicking Save,
     * and Alt-I is the same as toggling the Issue checkbox.
     */
    _onTextKeyPress: function(e) {
        e.stopPropagation();

        switch (e.which) {
            case 10:
            case $.ui.keyCode.ENTER:
                /* Enter */
                if (e.ctrlKey) {
                    this._ignoreKeyUp = true;
                    this._onSaveClicked();
                    return false;
                }
                break;

            case 73:
            case 105:
                /* I */
                if (e.altKey) {
                    this.model.set('openIssue', !this.model.get('openIssue'));
                    this._ignoreKeyUp = true;
                }
                break;

            default:
                this._ignoreKeyUp = false;
                break;
        }
    },

    /*
     * Callback for key up events in the text field.
     *
     * If the key isn't being ignored due to a special key combination
     * (Control-S, Alt-I), then we update the model with the new text.
     */
    _onTextKeyUp: function(e) {
        /*
         * We check if we want to ignore this event. The state from
         * some shortcuts (control-enter) may not be settled, and we
         * may end up setting this to dirty, causing page leave
         * confirmations.
         */
        if (!this._ignoreKeyUp) {
            e.stopPropagation();

            this.model.set('text', this._$textField.val());
        }
    }
}, {
    /*
     * Add some useful singletons to CommentDialogView for managing
     * comment dialogs.
     */

    _instance: null,

    /*
     * Creates and shows a new comment dialog and associated model.
     *
     * This is a class method that handles providing a comment dialog
     * ready to use with the given state.
     *
     * Only one comment dialog can appear on the screen at any given time
     * when using this.
     */
    create: function(options) {
        var instance = RB.CommentDialogView._instance,
            dlg;

        function showCommentDlg() {
            try {
                dlg.open(options.fromEl);
            } catch(e) {
                dlg.close();
                throw e;
            }

            RB.CommentDialogView._instance = dlg;
        }

        options = options || {};

        dlg = new RB.CommentDialogView({
            animate: options.animate,
            commentIssueManager: gCommentIssueManager,
            reviewRequestURL: options.reviewRequestURL,
            model: new RB.CommentEditor({
                comment: options.comment,
                publishedComments: options.publishedComments || undefined,
                publishedCommentsType: options.publishedCommentsType ||
                                       undefined
            })
        });

        dlg.render().$el
            .css('z-index', 999) // XXX Use classes for z-indexes.
            .appendTo(options.container || document.body);

        options.position = options.position || {};

        if (_.isFunction(options.position)) {
            options.position(dlg);
        } else if (options.position.beside) {
            var beside = options.position.beside;
            dlg.positionBeside(beside.el, beside);
        } else {
            var x = options.position.x,
                y = options.position.y;

            if (x === undefined) {
                /* Center it. */
                x = $(document).scrollLeft() +
                    ($(window).width() - dlg.$el.width()) / 2;
            }

            if (y === undefined) {
                /* Center it. */
                y = $(document).scrollTop() +
                    ($(window).height() - dlg.$el.height()) / 2;
            }

            dlg.move(x, y);
        }

        dlg.on('closed', function() {
            RB.CommentDialogView._instance = null;
        });

        if (instance) {
            instance.on('closed', showCommentDlg);
            instance.close();
        } else {
            showCommentDlg();
        }

        return dlg;
    }
});

<commit_msg>Assert that a comment is passed to CommentDialogView.<commit_after>/*
 * Displays a list of existing comments within a comment dialog.
 *
 * Each comment in the list is an existing, published comment that a user
 * has made. They will be displayed, along with any issue states and
 * identifying information, and links for viewing the comment on the review
 * or replying to it.
 *
 * This is used internally in CommentDialogView.
 */
var CommentsListView = Backbone.View.extend({
    itemTemplate: _.template([
        '<li class="<%= itemClass %>">',
         '
',
          '<%- comment.user.name %>',
          '',
           '<a href="<%= comment.url %>">View',
           '<a href="<%= reviewRequestURL %>',
                    '?reply_id=<%= comment.comment_id %>',
                    '&reply_type=<%= replyType %>">Reply',
          '',
         '
',
         '
<%- comment.text %>
'
    ].join('')),

    /*
     * Set the list of displayed comments.
     */
    setComments: function(comments, replyType) {
        var reviewRequestURL = this.options.reviewRequestURL,
            commentIssueManager = this.options.commentIssueManager,
            odd = true,
            $items = $();

        if (comments.length === 0) {
            return;
        }

        _.each(comments, function(comment) {
            var $item = $(this.itemTemplate({
                comment: comment,
                itemClass: odd ? 'odd' : 'even',
                reviewRequestURL: reviewRequestURL,
                replyType: replyType
            }));

            if (comment.issue_opened) {
                var interactive = window['gEditable'],
                    $issue = $('
').issueIndicator();

                if (interactive) {
                    $issue.issueButtons();
                }

                $issue
                    .commentIssue(comment.review_id, comment.comment_id,
                                  replyType, comment.issue_status,
                                  interactive)
                    .appendTo($item);

                commentIssueManager.registerCallback(comment.comment_id,
                    function(issue_status) {
                        comment.issue_status = issue_status;
                    });
            }

            $items = $items.add($item);

            odd = !odd;
        }, this);

        this.$el
            .empty()
            .append($items);
    }
});

/*
 * A dialog that allows for creating, editing or deleting draft comments on
 * a diff or file attachment. The dialog can be moved around on the page.
 *
 * Any existing comments for the selected region will be displayed alongside
 * the dialog for reference. However, this dialog is not intended to be
 * used to reply to those comments.
 */
RB.CommentDialogView = Backbone.View.extend({
    DIALOG_TOTAL_HEIGHT: 250,
    SLIDE_DISTANCE: 10,
    COMMENTS_BOX_WIDTH: 280,
    FORM_BOX_WIDTH: 380,

    className: 'comment-dlg',
    template: _.template($('#comment-dlg-template').html()),

    events: {
        'click .buttons .cancel': '_onCancelClicked',
        'click .buttons .delete': '_onDeleteClicked',
        'click .buttons .save': '_onSaveClicked',
        'keydown textarea': '_onTextKeyDown',
        'keyup textarea': '_onTextKeyUp'
    },

    initialize: function() {
        this._ignoreKeyUp = false;
    },

    render: function() {
        this.options.animate = (this.options.animate !== false);

        this.$el
            .hide()
            .html(this.template());

        this._$draftForm    = this.$el.find('form');
        this._$commentsPane = this.$el.find('.other-comments');
        this._$buttons      = this._$draftForm.find(".buttons");
        this._$statusField  = this._$draftForm.find(".status");
        this._$issueOptions = this._$draftForm.find(".comment-issue-options");

        this._$issueField = this._$issueOptions.find('input')
            .bindProperty('checked', this.model, 'openIssue')
            .bindProperty('disabled', this.model, 'editing', {
                elementToModel: false,
                inverse: true
            });

        this._$saveButton = this._$buttons.find('input.save')
            .bindProperty('disabled', this.model, 'canSave', {
                elementToModel: false,
                inverse: true
            });

        this._$cancelButton = this._$buttons.find('input.cancel');

        this._$deleteButton = this._$buttons.find('input.delete')
            .bindVisibility(this.model, 'canDelete')
            .bindProperty('disabled', this.model, 'canDelete', {
                elementToModel: false,
                inverse: true
            });

        this._commentsList = new CommentsListView({
            el: this._$commentsPane.find('ul'),
            reviewRequestURL: this.options.reviewRequestURL,
            commentIssueManager: this.options.commentIssueManager
        });

        /*
         * We need to handle keypress here, rather than in events above,
         * because jQuery will actually handle it. Backbone fails to.
         */
        this._$textField = this._$draftForm.find('textarea')
            .keypress(_.bind(this._onTextKeyPress, this))
            .bindProperty('disabled', this.model, 'canEdit', {
                elementToModel: false,
                inverse: true
            })
            .bindProperty('value', this.model, 'text', {
                elementToModel: false
            });

        this.$el
            .css("position", "absolute")
            .mousedown(function(evt) {
                /*
                 * Prevent this from reaching the selection area, which will
                 * swallow the default action for the mouse down.
                 */
                evt.stopPropagation();
            })
            .proxyTouchEvents();

        if (!$.browser.msie || $.browser.version >= 9) {
            /*
             * resizable is pretty broken in IE 6/7.
             */
            var grip = $("")
                .addClass("ui-resizable-handle ui-resizable-grip")
                .attr("src", STATIC_URLS["rb/images/resize-grip.png"])
                .insertAfter(this._$buttons)
                .proxyTouchEvents();

            this.$el.resizable({
                handles: $.browser.mobileSafari ? "grip,se"
                                                : "grip,n,e,s,w,se,sw,ne,nw",
                transparent: true,
                resize: _.bind(this._handleResize, this)
            });

            /* Reset the opacity, which resizable() changes. */
            grip.css("opacity", 100);
        }

        if (!$.browser.msie || $.browser.version >= 7) {
            /*
             * draggable works in IE7 and up, but not IE6.
             */
            this.$el.draggable({
                handle: $(".title", this).css("cursor", "move")
            });
        }

        this.model.on('change:dirty', function() {
            if (this.$el.is(':visible')) {
                this._handleResize();
            }
        }, this);

        this.model.on('change:statusText', function(model, text) {
            this._$statusField.text(text || '');
        }, this);

        this.model.on('change:publishedComments',
                      this._onPublishedCommentsChanged, this);
        this._onPublishedCommentsChanged();

        return this;
    },

    /*
     * Opens the comment dialog and focuses the text field.
     */
    open: function(fromEl) {
        function openDialog() {
            this.$el.scrollIntoView();
            this._$textField.focus();
        }

        this.$el
            .css({
                top: parseInt(this.$el.css("top"), 10) - this.SLIDE_DISTANCE,
                opacity: 0
            })
            .show();

        this._handleResize()

        if (this.model.get('canEdit')) {
            this.model.beginEdit();
        }

        if (this.options.animate) {
            this.$el.animate({
                top: "+=" + this.SLIDE_DISTANCE + "px",
                opacity: 1
            }, 350, "swing", _.bind(openDialog, this));
        } else {
            openDialog.call(this);
        }
    },

    /*
     * Closes the comment dialog, discarding the comment block if empty.
     *
     * This can optionally take a callback and context to notify when the
     * dialog has been closed.
     */
    close: function(onClosed, context) {
        function closeDialog() {
            this.model.close();
            this.$el.remove();
            this.trigger("closed");

            if (_.isFunction(onClosed)) {
                onClosed.call(context);
            };
        }

        if (this.options.animate && this.$el.is(":visible")) {
            this.$el.animate({
                top: "-=" + this.SLIDE_DISTANCE + "px",
                opacity: 0
            }, 350, "swing", _.bind(closeDialog, this));
        } else {
            closeDialog.call(this);
        }
    },

    /*
     * Moves the comment dialog to the given coordinates.
     */
    move: function(x, y) {
        this.$el.move(x, y);
    },

    /*
     * Positions the dialog beside an element.
     *
     * This takes the same arguments that $.fn.positionToSide takes.
     */
    positionBeside: function($el, options) {
        this.$el.positionToSide($el, options);
    },

    /*
     * Callback for when the list of published comments changes.
     *
     * Sets the list of comments in the CommentsList, and factors in some
     * new layout properties.
     */
    _onPublishedCommentsChanged: function() {
        var comments = this.model.get('publishedComments') || [],
            showComments = (comments.length > 0),
            width = this.FORM_BOX_WIDTH;

        this._commentsList.setComments(comments,
                                       this.model.get('publishedCommentsType'));
        this._$commentsPane.setVisible(showComments);

        /* Do this here so that calculations can be done before open() */

        if (showComments) {
            width += this.COMMENTS_BOX_WIDTH;
        }

        /* Don't let the text field bump up the size we set below. */
        this._$textField
            .width(10)
            .height(10);

        this.$el
            .width(width)
            .height(this.DIALOG_TOTAL_HEIGHT);
    },

    /*
     * Handles the resize of the comment dialog. This will lay out the
     * elements in the dialog appropriately.
     */
    _handleResize: function() {
        var $draftForm = this._$draftForm,
            $commentsPane = this._$commentsPane,
            $commentsList = this._commentsList.$el,
            $textField = this._$textField,
            textFieldPos,
            width = this.$el.width(),
            height = this.$el.height(),
            formWidth = width - $draftForm.getExtents("bp", "lr"),
            boxHeight = height,
            commentsWidth = 0;

        if ($commentsPane.is(":visible")) {
            $commentsPane
                .width(this.COMMENTS_BOX_WIDTH -
                       $commentsPane.getExtents("bp", "lr"))
                .height(boxHeight - $commentsPane.getExtents("bp", "tb"))
                .move(0, 0, "absolute");

            $commentsList.height($commentsPane.height() -
                                 $commentsList.position().top -
                                 $commentsList.getExtents("bmp", "b"));

            commentsWidth = $commentsPane.outerWidth(true);
            formWidth -= commentsWidth;
        }

        $draftForm
            .width(formWidth)
            .height(boxHeight - $draftForm.getExtents("bp", "tb"))
            .move(commentsWidth, 0, "absolute");

        textFieldPos = $textField.position();

        $textField
            .width($draftForm.width() - textFieldPos.left -
                   $textField.getExtents("bmp", "r"))
            .height($draftForm.height() - textFieldPos.top -
                    this._$buttons.outerHeight(true) -
                    this._$statusField.height() -
                    this._$issueOptions.height() -
                    $textField.getExtents("bmp", "b"));
    },

    /*
     * Callback for when the Cancel button is pressed.
     *
     * Cancels the comment (which may delete the comment block, if it's new)
     * and closes the dialog.
     */
    _onCancelClicked: function() {
        this.model.cancel();
        this.close();
    },

    /*
     * Callback for when the Delete button is pressed.
     *
     * Deletes the comment and closes the dialog.
     */
    _onDeleteClicked: function() {
        if (this.model.get('canDelete')) {
            this.model.deleteComment();
            this.close();
        }
    },

    /*
     * Callback for when the Save button is pressed.
     *
     * Saves the comment, creating it if it's new, and closes the dialog.
     */
    _onSaveClicked: function() {
        if (this.model.get('canSave')) {
            this.model.save();
            this.close();
        }
    },

    /*
     * Callback for key down events in the text field.
     *
     * If the Escape key is pressed, the dialog will be closed.
     *
     * The keydown event won't be propagated to the parent elements.
     */
    _onTextKeyDown: function(e) {
        e.stopPropagation();

        if (e.which === $.ui.keyCode.ESCAPE) {
            this._onCancelClicked();
            return false;
        }
    },

    /*
     * Callback for key press events in the text field.
     *
     * If the Control-Enter or Alt-I keys are pressed, we'll handle them
     * specially. Control-enter is the same thing as clicking Save,
     * and Alt-I is the same as toggling the Issue checkbox.
     */
    _onTextKeyPress: function(e) {
        e.stopPropagation();

        switch (e.which) {
            case 10:
            case $.ui.keyCode.ENTER:
                /* Enter */
                if (e.ctrlKey) {
                    this._ignoreKeyUp = true;
                    this._onSaveClicked();
                    return false;
                }
                break;

            case 73:
            case 105:
                /* I */
                if (e.altKey) {
                    this.model.set('openIssue', !this.model.get('openIssue'));
                    this._ignoreKeyUp = true;
                }
                break;

            default:
                this._ignoreKeyUp = false;
                break;
        }
    },

    /*
     * Callback for key up events in the text field.
     *
     * If the key isn't being ignored due to a special key combination
     * (Control-S, Alt-I), then we update the model with the new text.
     */
    _onTextKeyUp: function(e) {
        /*
         * We check if we want to ignore this event. The state from
         * some shortcuts (control-enter) may not be settled, and we
         * may end up setting this to dirty, causing page leave
         * confirmations.
         */
        if (!this._ignoreKeyUp) {
            e.stopPropagation();

            this.model.set('text', this._$textField.val());
        }
    }
}, {
    /*
     * Add some useful singletons to CommentDialogView for managing
     * comment dialogs.
     */

    _instance: null,

    /*
     * Creates and shows a new comment dialog and associated model.
     *
     * This is a class method that handles providing a comment dialog
     * ready to use with the given state.
     *
     * Only one comment dialog can appear on the screen at any given time
     * when using this.
     */
    create: function(options) {
        var instance = RB.CommentDialogView._instance,
            dlg;

        function showCommentDlg() {
            try {
                dlg.open(options.fromEl);
            } catch(e) {
                dlg.close();
                throw e;
            }

            RB.CommentDialogView._instance = dlg;
        }

        console.assert(options.comment, 'A comment must be specified');

        options = options || {};

        dlg = new RB.CommentDialogView({
            animate: options.animate,
            commentIssueManager: gCommentIssueManager,
            reviewRequestURL: options.reviewRequestURL,
            model: new RB.CommentEditor({
                comment: options.comment,
                publishedComments: options.publishedComments || undefined,
                publishedCommentsType: options.publishedCommentsType ||
                                       undefined
            })
        });

        dlg.render().$el
            .css('z-index', 999) // XXX Use classes for z-indexes.
            .appendTo(options.container || document.body);

        options.position = options.position || {};

        if (_.isFunction(options.position)) {
            options.position(dlg);
        } else if (options.position.beside) {
            var beside = options.position.beside;
            dlg.positionBeside(beside.el, beside);
        } else {
            var x = options.position.x,
                y = options.position.y;

            if (x === undefined) {
                /* Center it. */
                x = $(document).scrollLeft() +
                    ($(window).width() - dlg.$el.width()) / 2;
            }

            if (y === undefined) {
                /* Center it. */
                y = $(document).scrollTop() +
                    ($(window).height() - dlg.$el.height()) / 2;
            }

            dlg.move(x, y);
        }

        dlg.on('closed', function() {
            RB.CommentDialogView._instance = null;
        });

        if (instance) {
            instance.on('closed', showCommentDlg);
            instance.close();
        } else {
            showCommentDlg();
        }

        return dlg;
    }
});

<|endoftext|>


<commit_before>/*
  Created by: Anton Debner 2016
*/

var WIDTH=900, HEIGHT=600;
var UpArrow=38, DownArrow=40, LeftArrow=37, RightArrow=39;
var canvas, ctx, keystate;
var tank;
var wall_width = 1;
var cell_size = 50;
var num_cells_x, num_cells_y; // Assigned when cells are created
var cells;

class Cell {

  constructor(x, y, i, j) {
    this.ind_x = i;
    this.ind_y = j;
    this.x = x;
    this.y = y;
    this.right_wall = true;
    this.bottom_wall = true;
  }

  draw() {
    var x = this.x;
    var y = this.y;
    var s = cell_size / 2;
    var w = wall_width / 2;
    if (this.bottom_wall) {
      ctx.fillRect(x-s, y+s-w, s*2, w*2);
    }
    if (this.right_wall) {
      ctx.fillRect(x+s-w, y-s, w*2, s*2);
    }
  }
};

function get_cell_neighbours(x, y) {
  var neighbours = [];
  for (x2 = x - 1; x2 < x + 1; x2++) {
    for (y2 = y - 1; y2 < y + 1; y2++) {
      if (x2 >= 0 && x2 < num_cells_x && y2 >= 0 && y2 < num_cells_y) {
        neighbours.push(cells[x2][y2]);
      }
    }
  }
  return neighbours;
}

function maze_generator_kruskal() {
  /*
    Uses randomized Kruskal's algorithm to generate a maze.
    https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Kruskal.27s_algorithm
  */

  function shuffle(a) {
    /*
      Shuffles array in place.
      Taken from http://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array-in-javascript
    */
    var j, x, i;
    for (i = a.length; i; i--) {
      j = Math.floor(Math.random() * i);
      x = a[i - 1];
      a[i - 1] = a[j];
      a[j] = x;
    }
  }

  function find_cell_set(cell, sets) {
    // Finds the set where the given cell is found.
    for (set in sets) {
      if (sets[set].has(cell)) return set;
    }
  }

  function join_cell_sets(cell_1, cell_2, sets) {
    /*
      Checks if given cells are in different sets, joins the sets and returns
      true. Otherwise returns false.
    */
    set_ind1 = find_cell_set(cell_1, sets);
    set_ind2 = find_cell_set(cell_2, sets);
    if (!(set_ind1 === set_ind2)) {
      var joined_set = new Set(function*() {
        yield* sets[set_ind1]; yield* sets[set_ind2]; }()
      );
      delete sets[set_ind1];
      delete sets[set_ind2];
      sets.push(joined_set);
      return true;
    }
    return false;
  }

  // Add all walls to arrays and create a set for each cell
  var right_walls = [];
  var bottom_walls = [];
  var cell_sets = [];
  for (i = 0; i < num_cells_x; i++) {
    for (j = 0; j < num_cells_y; j++) {
      cell = cells[i][j];
      cell_sets.push(new Set([cell]));
      right_walls.push(cell);
      bottom_walls.push(cell);
    }
  }

  // Shuffle walls to randomize the maze
  shuffle(right_walls);
  shuffle(bottom_walls);

  // These variables adjust the proportion of removed horizontal and vertical
  // walls.
  var horiz_prob = 0.6; // value must be 0 < x <= 1
  var vert_prob = 0.9; // value must be 0 < x <= 1
  var remove_anyway_prob = 0.4; // Probability for removing extra walls

  // Remove all walls between disconnected cells
  while (right_walls.length > 0 && bottom_walls.length > 0) {
    if (right_walls.length > 0 && Math.random() < vert_prob) {
      var cell = right_walls.pop();
      if (cell.ind_x + 1 < num_cells_x) {
        next_cell = cells[cell.ind_x+1][cell.ind_y];
        if (join_cell_sets(cell, next_cell, cell_sets)) {
          cell.right_wall = false;
        }
        // Randomly delete the wall anyway
        else if (Math.random() < remove_anyway_prob) cell.right_wall = false;
      }
    }
    if (bottom_walls.length > 0 && Math.random() < horiz_prob) {
      var cell = bottom_walls.pop();
      if (cell.ind_y + 1 < num_cells_y) {
        next_cell = cells[cell.ind_x][cell.ind_y+1];
        if (join_cell_sets(cell, next_cell, cell_sets)) {
          cell.bottom_wall = false;
        }
        // Randomly delete the wall anyway
        else if (Math.random() < remove_anyway_prob) cell.bottom_wall = false;
      }
    }
  }

}

class Tank {

  constructor() {
    this.x = 10;
    this.y = 10;
    this.color = "#000";
    this.size = 10;
    this.dir = 0; // degrees
    this.speed = 1;
    this.turn_speed = 5;
  }

  update() {
    var radians = this.dir * (Math.PI/180);
    if (keystate[UpArrow]) {
      this.x -= this.speed * Math.sin(radians);
      this.y -= this.speed * Math.cos(radians);
    }
    else if (keystate[DownArrow]) {
      this.x += this.speed * Math.sin(radians);
      this.y += this.speed * Math.cos(radians);
    }
    if (keystate[LeftArrow]) {
      this.dir += this.turn_speed;
      if (this.dir >= 360) this.dir -= 360;
    }
    else if (keystate[RightArrow]) {
      this.dir -= this.turn_speed;
      if (this.dir < 0) this.dir += 360;
    }
  }

  draw() {
    var x = this.x;
    var y = this.y;
    var s = this.size/2;
    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(-this.dir * (Math.PI/180));
    ctx.fillRect(-s, -s, s*2, s*2);
    ctx.fillRect(-s/2, -s*2, s, s*2);
    ctx.restore();
  }

};

function main() {
  /*
    Creates a new HTML5 canvas element, adds listeners for input and
    contains the main loop.
  */
  canvas = document.createElement("canvas")
  canvas.width = WIDTH;
  canvas.height = HEIGHT;
  ctx = canvas.getContext("2d");

  // Attempt to find a document element with specific id, otherwise attach the
  // canvas to document body.
  attach_to = document.getElementById('game_window');
  if (attach_to == null)
  {
      attach_to = document.body;
  }
  attach_to.appendChild(canvas);

  // Add listeners for keydown and keyup
  keystate = {};
  document.addEventListener("keydown", function(evt) {
    if (evt.keyCode === UpArrow || evt.keyCode === DownArrow) {
      // Prevent up and down arrows from scrolling the website
      evt.preventDefault();
    }
    keystate[evt.keyCode] = true;
  });

  document.addEventListener("keyup", function(evt) {
    delete keystate[evt.keyCode];
  });

  tank = new Tank();

  init();
  var loop = function() {
    /*
      The main loop where all the magic happens.
    */
    update();
    draw();
    window.requestAnimationFrame(loop, canvas);
  };
  window.requestAnimationFrame(loop, canvas);
}

function init() {
  /*
    Sets gameobjects to their starting values.
  */
  keystate = {}; // Reset the keystate to avoid stuck buttons
  cells = [];
  num_cells_x = Math.round(WIDTH / cell_size);
  num_cells_y = Math.round(HEIGHT / cell_size);
  for (i = 0; i < num_cells_x; i++) {
    var x = i * cell_size + cell_size / 2;
    var column = []

    for (j = 0; j < num_cells_y; j++) {
      var y = j * cell_size + cell_size / 2;
      new_cell = new Cell(x, y, i, j);
      column[j] = new_cell;
    }
    cells[i] = column;
  }
  maze_generator_kruskal();
}

function update() {
  /*
    Handles game logic by moving all objects and checking collisions.
  */
  tank.update();
}

function draw() {
  /*
    Handles all drawing on the HTML5 canvas.
  */
  ctx.fillStyle = "#fff";
  ctx.fillRect(0, 0, WIDTH, HEIGHT);
  //ctx.save(); // Save current drawing state

  // Draw borders
  ctx.fillStyle = "#000";
  ctx.moveTo(0,0);
  ctx.lineTo(WIDTH, 0);
  ctx.lineTo(WIDTH, HEIGHT);
  ctx.lineTo(0, HEIGHT);
  ctx.lineTo(0, 0);
  ctx.stroke();

  tank.draw();
  num_cells_x = Math.round(WIDTH / cell_size);
  num_cells_y = Math.round(HEIGHT / cell_size);
  for (i = 0; i < num_cells_x; i++) {
    for (j = 0; j < num_cells_y; j++) {
      cells[i][j].draw();
    }
  }

  //ctx.restore(); // Restore saved drawing state
}

main();
<commit_msg>Implement collision detection<commit_after>/*
  Created by: Anton Debner 2016
*/

var WIDTH=900, HEIGHT=600;
var UpArrow=38, DownArrow=40, LeftArrow=37, RightArrow=39;
var canvas, ctx, keystate;
var tank;
var wall_width = 2;
var cell_size = 50;
var num_cells_x, num_cells_y; // Assigned when cells are created
var cells;

class Cell {

  constructor(x, y, i, j) {
    this.ind_x = i;
    this.ind_y = j;
    this.x = x;
    this.y = y;
    this.right_wall = true;
    this.bottom_wall = true;
  }

  draw() {
    var x = this.x;
    var y = this.y;
    var s = cell_size / 2;
    var w = wall_width / 2;
    if (this.bottom_wall) {
      ctx.fillRect(x-s, y+s-w, s*2, w*2);
    }
    if (this.right_wall) {
      ctx.fillRect(x+s-w, y-s, w*2, s*2);
    }
  }
};

function RectRectIntersect(ax, ay, aw, ah, bx, by, bw, bh) {
  /*
    Used for checking collisions between two rectangles.
  */
  var axIntersect = ax < (bx + bw);
  var bxIntersect = bx < (ax + aw);
  var ayIntersect = ay < (by + bh);
  var byIntersect = by < (ay + ah);
  var collision = {};
  //console.log(ax);
  if (axIntersect && bxIntersect && ayIntersect && byIntersect) {
    // Collision detected, calculate intersecting distances from all
    // directions.

    collision["up"] = ay - (by+bh); // How much the rect intersects from below
    collision["down"] = ay+ah - by; // How much the rect intersects from above
    collision["left"] = ax+aw - bx; // ... from left
    collision["right"] = ax - (bx+bw); // ... from right
    var min = collision["up"];
    var min_key = "up";
    for (direction in collision) {
      var value = collision[direction];
      if (Math.abs(value) < Math.abs(min)) {
        min = value;
        min_key = direction;
      }

    }
    collision["direction"] = min_key;
    collision["collision"] = true;
  }
  else {
    collision["collision"] = false;
  }
  return collision;
}

function RectWallIntersect(ax, ay, aw, ah) {
  /*
    Checks collisions between given rectangle and all walls.
  */
  var collisions = [];

  for (column_ind in cells) {
    column = cells[column_ind];
    for (cell_ind in column) {
      cell = column[cell_ind];
      var x = cell.x;
      var y = cell.y;
      var s = cell_size / 2;
      var w = wall_width / 2;
      if (cell.bottom_wall) {
        var collision = RectRectIntersect(ax, ay, aw, ah, x-s, y+s-w, s*2, w*2);
        if (collision["collision"] === true) collisions.push(collision);
      }
      if (cell.right_wall) {
        var collision = RectRectIntersect(ax, ay, aw, ah, x+s-w, y-s, w*2, s*2);
        if (collision["collision"] === true) collisions.push(collision);
      }
    }
  }
  return collisions;
}

function get_cell_neighbours(x, y) {
  var neighbours = [];
  for (x2 = x - 1; x2 < x + 1; x2++) {
    for (y2 = y - 1; y2 < y + 1; y2++) {
      if (x2 >= 0 && x2 < num_cells_x && y2 >= 0 && y2 < num_cells_y) {
        neighbours.push(cells[x2][y2]);
      }
    }
  }
  return neighbours;
}

function maze_generator_kruskal() {
  /*
    Uses randomized Kruskal's algorithm to generate a maze.
    https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_Kruskal.27s_algorithm
  */

  function shuffle(a) {
    /*
      Shuffles array in place.
      Taken from http://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array-in-javascript
    */
    var j, x, i;
    for (i = a.length; i; i--) {
      j = Math.floor(Math.random() * i);
      x = a[i - 1];
      a[i - 1] = a[j];
      a[j] = x;
    }
  }

  function find_cell_set(cell, sets) {
    // Finds the set where the given cell is found.
    for (set in sets) {
      if (sets[set].has(cell)) return set;
    }
  }

  function join_cell_sets(cell_1, cell_2, sets) {
    /*
      Checks if given cells are in different sets, joins the sets and returns
      true. Otherwise returns false.
    */
    set_ind1 = find_cell_set(cell_1, sets);
    set_ind2 = find_cell_set(cell_2, sets);
    if (!(set_ind1 === set_ind2)) {
      var joined_set = new Set(function*() {
        yield* sets[set_ind1]; yield* sets[set_ind2]; }()
      );
      delete sets[set_ind1];
      delete sets[set_ind2];
      sets.push(joined_set);
      return true;
    }
    return false;
  }

  // Add all walls to arrays and create a set for each cell
  var right_walls = [];
  var bottom_walls = [];
  var cell_sets = [];
  for (i = 0; i < num_cells_x; i++) {
    for (j = 0; j < num_cells_y; j++) {
      cell = cells[i][j];
      cell_sets.push(new Set([cell]));
      right_walls.push(cell);
      bottom_walls.push(cell);
    }
  }

  // Shuffle walls to randomize the maze
  shuffle(right_walls);
  shuffle(bottom_walls);

  // These variables adjust the proportion of removed horizontal and vertical
  // walls.
  var horiz_prob = 0.6; // value must be 0 < x <= 1
  var vert_prob = 0.9; // value must be 0 < x <= 1
  var remove_anyway_prob = 0.4; // Probability for removing extra walls

  // Remove all walls between disconnected cells
  while (right_walls.length > 0 && bottom_walls.length > 0) {
    if (right_walls.length > 0 && Math.random() < vert_prob) {
      var cell = right_walls.pop();
      if (cell.ind_x + 1 < num_cells_x) {
        next_cell = cells[cell.ind_x+1][cell.ind_y];
        if (join_cell_sets(cell, next_cell, cell_sets)) {
          cell.right_wall = false;
        }
        // Randomly delete the wall anyway
        else if (Math.random() < remove_anyway_prob) cell.right_wall = false;
      }
    }
    if (bottom_walls.length > 0 && Math.random() < horiz_prob) {
      var cell = bottom_walls.pop();
      if (cell.ind_y + 1 < num_cells_y) {
        next_cell = cells[cell.ind_x][cell.ind_y+1];
        if (join_cell_sets(cell, next_cell, cell_sets)) {
          cell.bottom_wall = false;
        }
        // Randomly delete the wall anyway
        else if (Math.random() < remove_anyway_prob) cell.bottom_wall = false;
      }
    }
  }

}

class Tank {

  constructor() {
    this.x = 10;
    this.y = 10;
    this.color = "#000";
    this.size = 10;
    this.dir = 0; // degrees
    this.speed = 1;
    this.turn_speed = 5;
  }

  update() {
    var radians = this.dir * (Math.PI/180);
    if (keystate[UpArrow]) {
      this.x -= this.speed * Math.sin(radians);
      this.y -= this.speed * Math.cos(radians);
    }
    else if (keystate[DownArrow]) {
      this.x += this.speed * Math.sin(radians);
      this.y += this.speed * Math.cos(radians);
    }
    if (keystate[LeftArrow]) {
      this.dir += this.turn_speed;
      if (this.dir >= 360) this.dir -= 360;
    }
    else if (keystate[RightArrow]) {
      this.dir -= this.turn_speed;
      if (this.dir < 0) this.dir += 360;
    }
    var s = this.size/2;
    var collisions = RectWallIntersect(this.x-s, this.y-s, s*2, s*2);
    var prev_x = this.x;
    var prev_y = this.y;
    var ok = false;
    while(ok === false) {
      ok = true;
      for (i = 0; i < collisions.length; i++) {
        console.log("COLLISION!");
        console.log(collisions.length);
        console.log(collisions);
        var collision = collisions[i];
        var key = collision["direction"];
        if (key == "up" || key == "down") {
          console.log("Moving down/up");
          this.y -= collision[key];
        }
        else if (key == "left" || key == "right") {
          console.log("Moving left/right");
          this.x -= collision[key];
        }
        else console.log("WHAAAAAAAAAAAAAAT");
        var new_collisions = RectWallIntersect(this.x-s, this.y-s, s*2, s*2);
        console.log(new_collisions);
        if (new_collisions.length === 0) {
          break;
        }
        else if (i < collisions.length-1) {
          console.log("Ei toimi");
          this.x = prev_x;
          this.y = prev_y;
        }
        else {
          collisions = new_collisions;
          ok = false;
          console.log("Uusiks");
        }
      }

    }
    console.log("PLÖP");
  }

  draw() {
    var x = this.x;
    var y = this.y;
    var s = this.size/2;
    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(-this.dir * (Math.PI/180));
    ctx.fillRect(-s, -s, s*2, s*2);
    ctx.fillRect(-s/2, -s*2, s, s*2);
    ctx.restore();
  }

};

function main() {
  /*
    Creates a new HTML5 canvas element, adds listeners for input and
    contains the main loop.
  */
  canvas = document.createElement("canvas")
  canvas.width = WIDTH;
  canvas.height = HEIGHT;
  ctx = canvas.getContext("2d");

  // Attempt to find a document element with specific id, otherwise attach the
  // canvas to document body.
  attach_to = document.getElementById('game_window');
  if (attach_to == null)
  {
      attach_to = document.body;
  }
  attach_to.appendChild(canvas);

  // Add listeners for keydown and keyup
  keystate = {};
  document.addEventListener("keydown", function(evt) {
    if (evt.keyCode === UpArrow || evt.keyCode === DownArrow) {
      // Prevent up and down arrows from scrolling the website
      evt.preventDefault();
    }
    keystate[evt.keyCode] = true;
  });

  document.addEventListener("keyup", function(evt) {
    delete keystate[evt.keyCode];
  });

  tank = new Tank();

  init();
  var loop = function() {
    /*
      The main loop where all the magic happens.
    */
    update();
    draw();
    window.requestAnimationFrame(loop, canvas);
  };
  window.requestAnimationFrame(loop, canvas);
}

function init() {
  /*
    Sets gameobjects to their starting values.
  */
  keystate = {}; // Reset the keystate to avoid stuck buttons
  cells = [];
  num_cells_x = Math.round(WIDTH / cell_size);
  num_cells_y = Math.round(HEIGHT / cell_size);
  for (i = 0; i < num_cells_x; i++) {
    var x = i * cell_size + cell_size / 2;
    var column = []

    for (j = 0; j < num_cells_y; j++) {
      var y = j * cell_size + cell_size / 2;
      new_cell = new Cell(x, y, i, j);
      column[j] = new_cell;
    }
    cells[i] = column;
  }
  maze_generator_kruskal();
}

function update() {
  /*
    Handles game logic by moving all objects and checking collisions.
  */
  tank.update();
}

function draw() {
  /*
    Handles all drawing on the HTML5 canvas.
  */
  ctx.fillStyle = "#fff";
  ctx.fillRect(0, 0, WIDTH, HEIGHT);
  //ctx.save(); // Save current drawing state

  // Draw borders
  ctx.fillStyle = "#000";
  ctx.moveTo(0,0);
  ctx.lineTo(WIDTH, 0);
  ctx.lineTo(WIDTH, HEIGHT);
  ctx.lineTo(0, HEIGHT);
  ctx.lineTo(0, 0);
  ctx.stroke();

  tank.draw();
  num_cells_x = Math.round(WIDTH / cell_size);
  num_cells_y = Math.round(HEIGHT / cell_size);
  for (i = 0; i < num_cells_x; i++) {
    for (j = 0; j < num_cells_y; j++) {
      cells[i][j].draw();
    }
  }

  //ctx.restore(); // Restore saved drawing state
}

main();
<|endoftext|>


<commit_before>"use strict";

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

var _path = require("path");

var _tape = require("tape");

var _tape2 = _interopRequireDefault(_tape);

var _memoryFs = require("memory-fs");

var _memoryFs2 = _interopRequireDefault(_memoryFs);

var _webpack = require("webpack");

var _webpack2 = _interopRequireDefault(_webpack);

var _index = require("../index");

var _index2 = _interopRequireDefault(_index);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var EXPECTED_FILENAME_LIST = ["CHANGELOG.md", "README.md", "src/__tests__/index.spec.js", "package.json"];

(0, _tape2.default)("UnusedFilesWebpackPlugin", function testUnusedFilesWebpackPlugin(t) {
  var compiler = (0, _webpack2.default)({
    context: (0, _path.resolve)(__dirname, "../../"),
    entry: {
      UnusedFilesWebpackPlugin: (0, _path.resolve)(__dirname, "../index.js")
    },
    output: {
      path: __dirname },
    // It will be in MemoryFS :)
    plugins: [new _index2.default()]
  });
  compiler.outputFileSystem = new _memoryFs2.default();

  compiler.run(function (err, stats) {
    t.equal(err, null);

    var warnings = stats.compilation.warnings;

    t.equal(warnings.length, 1);

    var _warnings = _slicedToArray(warnings, 1);

    var unusedFilesError = _warnings[0];

    t.equal(unusedFilesError instanceof Error, true);

    var message = unusedFilesError.message;

    var containsExpected = EXPECTED_FILENAME_LIST.every(function (filename) {
      return message.match(filename);
    });
    t.equal(containsExpected, true);

    t.end();
  });
});<commit_msg>chore(lib): compile from src using babel<commit_after>"use strict";

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

var _path = require("path");

var _tape = require("tape");

var _tape2 = _interopRequireDefault(_tape);

var _memoryFs = require("memory-fs");

var _memoryFs2 = _interopRequireDefault(_memoryFs);

var _webpack = require("webpack");

var _webpack2 = _interopRequireDefault(_webpack);

var _index = require("../index");

var _index2 = _interopRequireDefault(_index);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var EXPECTED_FILENAME_LIST = ["CHANGELOG.md", "README.md", "src/__tests__/index.spec.js", "package.json"];

(0, _tape2.default)("UnusedFilesWebpackPlugin", function (t) {
  var compiler = (0, _webpack2.default)({
    context: (0, _path.resolve)(__dirname, "../../"),
    entry: {
      UnusedFilesWebpackPlugin: (0, _path.resolve)(__dirname, "../index.js")
    },
    output: {
      path: __dirname },
    // It will be in MemoryFS :)
    plugins: [new _index2.default()]
  });
  compiler.outputFileSystem = new _memoryFs2.default();

  compiler.run(function (err, stats) {
    t.equal(err, null);

    var warnings = stats.compilation.warnings;

    t.equal(warnings.length, 1);

    var _warnings = _slicedToArray(warnings, 1);

    var unusedFilesError = _warnings[0];

    t.equal(unusedFilesError instanceof Error, true);

    var message = unusedFilesError.message;

    var containsExpected = EXPECTED_FILENAME_LIST.every(function (filename) {
      return message.match(filename);
    });
    t.equal(containsExpected, true);

    t.end();
  });
});<|endoftext|>


<commit_before>var Browser = require('../interfaces/browser.js');
var objectAssign = require('object-assign');

function WebDriverIOAdaptor(webdriverioInstance) {
  this._webdriverio = webdriverioInstance;
}

WebDriverIOAdaptor.prototype = objectAssign({}, Browser, {
  takeScreenshot: function(callback) {
    this._webdriverio.saveScreenshot(callback);
  },

  getBoundingClientRect: function(selector, callback) {
    this._webdriverio.getElementSize(selector, function(error, size) {
      if (error) {
        return callback(error);
      }

      this._webdriverio.getLocation(selector, function(error, location) {
        if (error) {
          return callback(error);
        }

        var rect = {
          width: size.width,
          height: size.height,
          top: location.y,
          left: location.x,
          bottom: location.y + size.height,
          right: location.x + size.width
        };

        callback(null, rect);
      });
    });
  }
});

module.exports = WebDriverIOAdaptor;
<commit_msg>solve webdriverio adaptor scoping problem<commit_after>var Browser = require('../interfaces/browser.js');
var objectAssign = require('object-assign');

function WebDriverIOAdaptor(webdriverioInstance) {
  this._webdriverio = webdriverioInstance;
}

WebDriverIOAdaptor.prototype = objectAssign({}, Browser, {
  takeScreenshot: function(callback) {
    this._webdriverio.saveScreenshot(callback);
  },

  getBoundingClientRect: function(selector, callback) {
    var webdriverio = this._webdriverio;

    webdriverio.getElementSize(selector, function(error, size) {
      if (error) {
        return callback(error);
      }

      webdriverio.getLocation(selector, function(error, location) {
        if (error) {
          return callback(error);
        }

        var rect = {
          width: size.width,
          height: size.height,
          top: location.y,
          left: location.x,
          bottom: location.y + size.height,
          right: location.x + size.width
        };

        callback(null, rect);
      });
    });
  }
});

module.exports = WebDriverIOAdaptor;
<|endoftext|>


<commit_before>'use strict';

var os = require('os');
var path = require('path');
var grunt = require('grunt');
var git = require('nodegit');

var fixtures = path.join(__dirname, 'fixtures');
var gitFiles = path.join(fixtures, 'git-files');
var spawn = require('child_process').spawn;
var tmpdir = path.join(os.tmpdir(), 'grunt-copy-git-index');
var gitDir = path.join(tmpdir, 'git-repository');
var indexFilesDir = path.join(tmpdir, 'index-files');
var repository;

describe('grunt-copy-git-index', function () {
  describe('when run with no options specified', function () {
    before(function (done) {
      // create git repository

      grunt.file.delete(gitDir, {force: true});
      grunt.file.mkdir(gitDir);

      grunt.file.recurse(gitFiles, function (abspath, rootdir, subdir, filename) {
        grunt.file.copy(abspath, path.join(gitDir, subdir || '', filename));
      });
      git.Repository.init(gitDir, 0).then(function (repo) {
        repository = repo;
        console.log('git workdir:', repo.workdir());
        return repo.index();
      }).then(function (index) {
        var returnCode = index.addByPath('app.js') || index.write();
        if (returnCode) return done(new Error('git add files failed with code ' + returnCode));
        console.log('git add files success');
        done();
      }, done);
    });

    it('should copy files from git index', function () {
    });
  });
});
<commit_msg>unittest create a git respository<commit_after>'use strict';

var os = require('os');
var path = require('path');
var grunt = require('grunt');
var git = require('nodegit');

var fixtures = path.join(__dirname, 'fixtures');
var gitFiles = path.join(fixtures, 'git-files');
var gitFilesInRepository = path.join(gitFiles, 'repository');
var gitFilesInIndex = path.join(gitFiles, 'index');
var gitFilesInWork = path.join(gitFiles, 'work');
var spawn = require('child_process').spawn;
var tmpdir = path.join(os.tmpdir(), 'grunt-copy-git-index');
var gitDir = path.join(tmpdir, 'git-repository');
var indexFilesDir = path.join(tmpdir, 'index-files');

describe('grunt-copy-git-index', function () {
  describe('when run with no options specified', function () {
    before(function (done) {
      // Create git repository

      var promiseResults = {};
      grunt.file.delete(gitDir, {force: true});
      grunt.file.mkdir(gitDir);

      git.Repository.init(gitDir, 0).then(function (repo) {
        promiseResults.repository = repo;
        console.log('git workdir:', repo.workdir());
        return repo.index();
      }).then(function (index) {
        promiseResults.index = index;
        grunt.file.recurse(gitFilesInRepository, function (abspath, rootdir, subdir, filename) {
          grunt.file.copy(abspath, path.join(gitDir, subdir || '', filename));
        });
        return index.addAll();
      }).then(function (returnCode) {
        if (returnCode || promiseResults.index.write()) throw new Error('git add files failed with code ' + returnCode);
        console.log('git add files success');

        // Commit files
        return promiseResults.index.writeTree();
      }).then(function (oid) {
        promiseResults.oid = oid;
        var author = git.Signature.now('wmzy', '1256573276@qq.com');
        var committer = author;
        return promiseResults.repository
          .createCommit('HEAD', author, committer, 'first commit, no HEAD', oid, []);
      }).then(function (commitId) {
        grunt.file.recurse(gitFilesInIndex, function (abspath, rootdir, subdir, filename) {
          grunt.file.copy(abspath, path.join(gitDir, subdir || '', filename));
        });
        return promiseResults.index.addAll();
      }).then(function (returnCode) {
        if (returnCode || promiseResults.index.write()) throw new Error('git add files failed with code ' + returnCode);
        console.log('git add files success');

        grunt.file.recurse(gitFilesInWork, function (abspath, rootdir, subdir, filename) {
          grunt.file.copy(abspath, path.join(gitDir, subdir || '', filename));
        });

        done();
      }).catch(done);
    });

    it('should copy files from git index', function () {
    });
  });
});
<|endoftext|>


<commit_before>'use strict';

const BaseAgent = require('../base');

class TracingAgent extends BaseAgent {
  constructor() {
    super();
  }

  /**
   * Start trace events collection.
   *
   * @param {string=} categories Category/tag filter
   * @param {string=} options Tracing options
   * @param {number=} bufferUsageReportingInterval If set, the agent will issue bufferUsage events at this interval, specified in milliseconds
   */
  start() {
    throw new Error('Not implemented');
  }

  /**
   * Stop trace events collection.
   */
  end() {
    throw new Error('Not implemented');
  }

  /**
   * Gets supported tracing categories.
   *
   * @returns {Array.<string>} categories A list of supported tracing categories.
   */
  getCategories() {
    throw new Error('Not implemented');
  }

  /**
   * Request a global memory dump.
   *
   * @returns {string} dumpGuid GUID of the resulting global memory dump.
   * @returns {boolean} success True iff the global memory dump succeeded.
   */
  requestMemoryDump() {
    throw new Error('Not implemented');
  }
}

module.exports = TracingAgent;
<commit_msg>Most minimal tracing agent to not trigger errors<commit_after>'use strict';

const BaseAgent = require('../base');

const MICRO_PER_SECOND = 1e6;
const NANO_PER_MICRO = 1e3;

class TracingAgent extends BaseAgent {
  constructor() {
    super();
  }

  // Current tracing time in full microseconds
  _getTime() {
    const delta = process.hrtime(this._startTime);
    return (delta[0] * MICRO_PER_SECOND + delta[1] / NANO_PER_MICRO) | 0;
  }

  /**
   * Start trace events collection.
   *
   * @param {string=} categories Category/tag filter
   * @param {string=} options Tracing options
   * @param {number=} bufferUsageReportingInterval If set, the agent will issue bufferUsage events at this interval, specified in milliseconds
   */
  start(params) {
    this._ignore('start', params);
    this._startTime = process.hrtime();
    this._sessionId = 'bugger-session'; // TODO: generate unique id
  }

  /**
   * Stop trace events collection.
   */
  end() {
    this.emit('dataCollected', {
      value: [
        {
          args: { sessionId: this._sessionId },
          cat: 'disabled-by-default-devtools.timeline',
          name: 'TracingStartedInPage',
          ph: 'I', // Instant
          pid: process.pid,
          s: 'g',
          tid: 1,
          ts: this._getTime(),
        },
      ],
    });
    this.emit('tracingComplete', {});
  }

  /**
   * Gets supported tracing categories.
   *
   * @returns {Array.<string>} categories A list of supported tracing categories.
   */
  getCategories() {
    throw new Error('Not implemented');
  }

  /**
   * Request a global memory dump.
   *
   * @returns {string} dumpGuid GUID of the resulting global memory dump.
   * @returns {boolean} success True iff the global memory dump succeeded.
   */
  requestMemoryDump() {
    throw new Error('Not implemented');
  }
}

module.exports = TracingAgent;
<|endoftext|>


<commit_before>"use strict";

require("./fixtures/env")
var zeroClipboard, clip;

exports.dom = {
  setUp: function (callback) {
    zeroClipboard = require("../ZeroClipboard");
    clip = new zeroClipboard.Client();
    callback();
  },

  tearDown: function (callback) {
    zeroClipboard.destroy();
    callback();
  },

  "Changing movie path works": function (test) {

    // Test the client has default path
    test.equal(zeroClipboard._moviePath, "ZeroClipboard.swf");

    // change the path
    zeroClipboard.setMoviePath("new/movie/path.swf");
    test.equal(zeroClipboard._moviePath, "new/movie/path.swf");

    test.done();
  },

  "Set trusted domain": function (test) {

    // Test the _trustedDomain is undefined
    test.equal(zeroClipboard._trustedDomain, undefined);

    // change the path
    zeroClipboard.setTrustedDomain("google.com");
    test.equal(zeroClipboard._trustedDomain, "google.com");

    test.done();
  },

  "destroy clears up the client": function (test) {

    zeroClipboard.destroy();

    test.equal($("#global-zeroclipboard-html-bridge").length, 0);
    test.ok(!zeroClipboard._client);

    test.done();
  },

  "Detecting no flash": function (test) {
    navigator.mimeTypes["application/x-shockwave-flash"] = undefined;

    // Test that we don't have flash
    test.equal(zeroClipboard.detectFlashSupport(), false);

    navigator.mimeTypes["application/x-shockwave-flash"] = true;
    test.done();
  },

  "Detecting has flash mimetype": function (test) {

    // Test that we don't have flash
    test.equal(zeroClipboard.detectFlashSupport(), true);

    test.done();
  },

}<commit_msg>core not dom<commit_after>"use strict";

require("./fixtures/env")
var zeroClipboard, clip;

exports.core = {
  setUp: function (callback) {
    zeroClipboard = require("../ZeroClipboard");
    clip = new zeroClipboard.Client();
    callback();
  },

  tearDown: function (callback) {
    zeroClipboard.destroy();
    callback();
  },

  "Changing movie path works": function (test) {

    // Test the client has default path
    test.equal(zeroClipboard._moviePath, "ZeroClipboard.swf");

    // change the path
    zeroClipboard.setMoviePath("new/movie/path.swf");
    test.equal(zeroClipboard._moviePath, "new/movie/path.swf");

    test.done();
  },

  "Set trusted domain": function (test) {

    // Test the _trustedDomain is undefined
    test.equal(zeroClipboard._trustedDomain, undefined);

    // change the path
    zeroClipboard.setTrustedDomain("google.com");
    test.equal(zeroClipboard._trustedDomain, "google.com");

    test.done();
  },

  "destroy clears up the client": function (test) {

    zeroClipboard.destroy();

    test.equal($("#global-zeroclipboard-html-bridge").length, 0);
    test.ok(!zeroClipboard._client);

    test.done();
  },

  "Detecting no flash": function (test) {
    navigator.mimeTypes["application/x-shockwave-flash"] = undefined;

    // Test that we don't have flash
    test.equal(zeroClipboard.detectFlashSupport(), false);

    navigator.mimeTypes["application/x-shockwave-flash"] = true;
    test.done();
  },

  "Detecting has flash mimetype": function (test) {

    // Test that we don't have flash
    test.equal(zeroClipboard.detectFlashSupport(), true);

    test.done();
  },

}<|endoftext|>


<commit_before>'use strict';

module.exports = function(options) {
    var onejsCompiler = require('gulp-onejs-compiler');
    var tsc = require('gulp-typescript');
    var uglify = require('gulp-uglifyjs');
    var del = require('del');
    var less = require('gulp-less');
    var cssMinify = require('gulp-minify-css');
    var postcss = require('gulp-postcss');
    var autoprefixer = require('autoprefixer-core');
    var _ = require('lodash');
    var texttojs = require('gulp-texttojs');
    var htmlMinify = require('gulp-minify-html');
    var tslint = require('gulp-tslint');
    var gutil = require('gulp-util');
    var path = require('path');

    var gulp = options.gulp;
    var paths = options.paths;
    var autoprefixerOptions = options.autoprefixerOptions;
    var tscOptions = options.tscOptions;
    var tsLintOptions = options.tsLintOptions;
    var gulpTaskOptions = options.gulpTaskOptions;

    /** Removes all built files, keeping only source */
    gulp.task('nuke', _.union(['clean'], gulpTaskOptions['nuke']), function(cb) {
        del([
            paths.dist.root,
        ], cb);
    });

    /** Removes all built files EXCEPT dist directory for gulp watch purposes */
    gulp.task('clean', _.union(gulpTaskOptions['clean']), function(cb) {
        del([
            paths.temp.root,
            paths.app.root,
            paths.app.min.root,
            paths.release.root
        ], cb);
    });

    /** Copies app deps to their app path */
    gulp.task('copy-app-deps', _.union(['clean'], gulpTaskOptions['copy-app-deps']), function(cb) {
        _.map(paths.deps, function(value, key) {
            if (value instanceof Array) {
                _.map(value, function(v, k) {
                    gulp.src(key)
                        .pipe(gulp.dest(v));
                });
            } else {
                gulp.src(key)
                    .pipe(gulp.dest(value));
            }
        });
        cb();
    });

    /** Runs LESS compiler, auto-prefixer, and uglify, then creates js modules and outputs to temp folder */
    gulp.task('build-less', _.union(['clean'], gulpTaskOptions['build-less']), function() {
        return gulp.src(paths.src.lessGlob)
            .pipe(less())
            .pipe(postcss([autoprefixer(autoprefixerOptions)]))
            .pipe(cssMinify())
            .pipe(texttojs({
                template: function (file) {
                    var domUtilsPath = 'onejs/DomUtils';
                    if (paths.app.localRoot) {
                        var localPath = path.join(path.relative(paths.src.root, file.path));
                        domUtilsPath = path.relative(localPath, path.join(domUtilsPath)).replace(/\\/g, '/');
                    }
                    return "define(['" + domUtilsPath + "'], function(DomUtils) { DomUtils.loadStyles(<%= content %>); });";
                }
            }))
            .pipe(gulp.dest(paths.app.localRoot || paths.app.root))
    });

    /** Compiles OneJS html templates */
    gulp.task('build-templates', _.union(['clean'], gulpTaskOptions['build-templates']), function() {
        return gulp.src(paths.src.htmlGlob)
            .pipe(htmlMinify({
                comments: true
            }))
            .pipe(texttojs())
            .pipe(gulp.dest(paths.app.localRoot || paths.app.root));
    });

    /** Copies OneJS TypeScript files to temp directory for futher compilation */
    gulp.task('copy-typescript', _.union(['clean'], gulpTaskOptions['copy-typescript']), function() {
        gutil.log(gutil.colors.gray('Running tslint (using tslint.json) refer to https://github.com/palantir/tslint for more details on each rule.'));
        return gulp.src(paths.src.tsGlob)
            .pipe(gulp.dest(paths.temp.localRoot || paths.temp.root))
            .pipe(tslint(tsLintOptions))
            .pipe(tslint.report('verbose'));
    });

    /** Runs the basic pre-processing steps before compilation */
    gulp.task('build-app-preprocess', _.union(['build-templates', 'copy-typescript', 'build-less', 'copy-app-deps'], gulpTaskOptions['build-app-preprocess']));

    /** Runs the TypeScript amd compiler over your application .ts files */
    gulp.task('build-app-amd', _.union(['build-app-preprocess'], gulpTaskOptions['build-app-amd']), function() {
        return gulp.src(paths.temp.tsGlob)
            // Allow tscOption overrides, but ensure that we're targeting amd
            .pipe(tsc(_.merge(tscOptions, {module: 'amd'})))
            .pipe(gulp.dest(paths.app.localRoot || paths.app.root));
    });

    /** Runs the TypeScript commonjs compiler over your application .ts files */
    gulp.task('build-app-commonjs', _.union(['build-app-preprocess'], gulpTaskOptions['build-app-amd']), function() {
        return gulp.src(paths.temp.tsGlob)
            // Allow tscOption overrides, but ensure that we're targeting commonjs
            .pipe(tsc(_.merge(tscOptions, {module: 'commonjs'})))
            .pipe(gulp.dest(paths.app.localRoot || paths.app.root));
    });

    /** Watches your src folder for changes, and runs the default build task */
    gulp.task('watch', _.union([], gulpTaskOptions['watch']), function() {
        gulp.watch([paths.src.glob, paths.test.glob], ['default']);
    });

    /** Default dev task for building */
    gulp.task('build-app', _.union(['build-app-amd'], gulpTaskOptions['build-app']));

    /** Our default task, but can be overridden by the users gulp file */
    gulp.task('default', _.union(['build-app'], gulpTaskOptions['default']));
};
<commit_msg>Reconcile changes from Harry<commit_after>'use strict';

module.exports = function(options) {
    var onejsCompiler = require('gulp-onejs-compiler');
    var tsc = require('gulp-typescript');
    var uglify = require('gulp-uglifyjs');
    var del = require('del');
    var less = require('gulp-less');
    var cssMinify = require('gulp-minify-css');
    var postcss = require('gulp-postcss');
    var autoprefixer = require('autoprefixer-core');
    var _ = require('lodash');
    var texttojs = require('gulp-texttojs');
    var htmlMinify = require('gulp-minify-html');
    var tslint = require('gulp-tslint');
    var gutil = require('gulp-util');
    var path = require('path');

    var gulp = options.gulp;
    var paths = options.paths;
    var autoprefixerOptions = options.autoprefixerOptions;
    var tscOptions = options.tscOptions;
    var tsLintOptions = options.tsLintOptions;
    var gulpTaskOptions = options.gulpTaskOptions;

    /** Removes all built files, keeping only source */
    gulp.task('nuke', _.union(['clean'], gulpTaskOptions['nuke']), function(cb) {
        del([
            paths.dist.root,
        ], cb);
    });

    /** Removes all built files EXCEPT dist directory for gulp watch purposes */
    gulp.task('clean', _.union(gulpTaskOptions['clean']), function(cb) {
        del([
            paths.temp.root,
            paths.app.root,
            paths.app.min.root,
            paths.release.root
        ], cb);
    });

    /** Copies app deps to their app path */
    gulp.task('copy-app-deps', _.union(['clean'], gulpTaskOptions['copy-app-deps']), function(cb) {
        _.map(paths.deps, function(value, key) {
            if (value instanceof Array) {
                _.map(value, function(v, k) {
                    gulp.src(key)
                        .pipe(gulp.dest(v));
                });
            } else {
                gulp.src(key)
                    .pipe(gulp.dest(value));
            }
        });
        cb();
    });

    /** Runs LESS compiler, auto-prefixer, and uglify, then creates js modules and outputs to temp folder */
    gulp.task('build-less', _.union(['clean'], gulpTaskOptions['build-less']), function() {
        return gulp.src(paths.src.lessGlob)
            .pipe(less())
            .pipe(postcss([autoprefixer(autoprefixerOptions)]))
            .pipe(cssMinify())
            .pipe(texttojs({
                template: function (file) {
                    var domUtilsPath = 'onejs/DomUtils';
                    if (paths.app.localRoot) {
                        var localPath = path.join(path.relative(paths.src.root, file.path));
                        domUtilsPath = path.relative(localPath, path.join(domUtilsPath)).replace(/\\/g, '/');
                    }
                    return "define(['" + domUtilsPath + "'], function(DomUtils) { DomUtils.loadStyles(<%= content %>); });";
                }
            }))
            .pipe(gulp.dest(paths.app.localRoot || paths.app.root))
    });

    /** Compiles OneJS html templates */
    gulp.task('build-templates', _.union(['clean'], gulpTaskOptions['build-templates']), function() {
        return gulp.src(paths.src.htmlGlob)
            .pipe(htmlMinify({
                comments: true
            }))
            .pipe(texttojs())
            .pipe(gulp.dest(paths.app.localRoot || paths.app.root));
    });

    /** Copies TypeScript files to temp directory for futher compilation */
        gulp.task('copy-typescript', _.union(['clean'], gulpTaskOptions['copy-typescript']), function () {
            return gulp.src(paths.src.tsGlob)
                .pipe(gulp.dest(paths.temp.localRoot || paths.temp.root));
        });

        /** Lints the source TypeScript files */
        gulp.task('lint-typescript', _.union(gulpTaskOptions['lint-typescript']), function () {
            gutil.log(gutil.colors.gray('Running tslint (using tslint.json) refer to https://github.com/palantir/tslint for more details on each rule.'));
            return gulp.src(paths.src.tsLintGlob)
                .pipe(tslint(tsLintOptions))
                .pipe(tslint.report('verbose'));
        });

    /** Runs the basic pre-processing steps before compilation */
    gulp.task('build-app-preprocess', _.union(['build-templates', 'copy-typescript', 'build-less', 'copy-app-deps', 'lint-typescript'], gulpTaskOptions['build-app-preprocess']));

    /** Runs the TypeScript amd compiler over your application .ts files */
    gulp.task('build-app-amd', _.union(['build-app-preprocess'], gulpTaskOptions['build-app-amd']), function() {
        return gulp.src(paths.temp.tsGlob, { base: paths.temp.root })
            .pipe(through.obj(function (file, encoding, callback) {
                // Mark the file as excluded for piping later
                if (!paths.temp.tsFilter || paths.temp.tsFilter(file)) {
                    this.push(file);
                }
                callback();
            }))
            // Allow tscOption overrides, but ensure that we're targeting amd
            .pipe(tsc(_.merge(tscOptions, { module: 'amd' })))
            .pipe(gulp.dest(paths.app.root));
    });

    /** Runs the TypeScript commonjs compiler over your application .ts files */
    gulp.task('build-app-commonjs', _.union(['build-app-preprocess'], gulpTaskOptions['build-app-amd']), function() {
        return gulp.src(paths.temp.tsGlob, { base: paths.temp.root })
            .pipe(through.obj(function (file, encoding, callback) {
                // Mark the file as excluded for piping later
                if (!paths.temp.tsFilter || paths.temp.tsFilter(file)) {
                    this.push(file);
                }
                callback();
            }))
            // Allow tscOption overrides, but ensure that we're targeting commonjs
            .pipe(tsc(_.merge(tscOptions, { module: 'commonjs' })))
            .pipe(gulp.dest(paths.app.root));
    });

    /** Watches your src folder for changes, and runs the default build task */
    gulp.task('watch', _.union([], gulpTaskOptions['watch']), function() {
        gulp.watch([paths.src.glob, paths.test.glob], ['default']);
    });

    /** Default dev task for building */
    gulp.task('build-app', _.union(['build-app-amd'], gulpTaskOptions['build-app']));

    /** Our default task, but can be overridden by the users gulp file */
    gulp.task('default', _.union(['build-app'], gulpTaskOptions['default']));
};
<|endoftext|>