<commit_before>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

var angular = angular || null;

(function() {

    var module = angular.module('amara.SubtitleEditor', [
        'amara.SubtitleEditor.collab',
        'amara.SubtitleEditor.help',
        'amara.SubtitleEditor.modal',
        'amara.SubtitleEditor.dom',
        'amara.SubtitleEditor.lock',
        'amara.SubtitleEditor.workflow',
        'amara.SubtitleEditor.subtitles.controllers',
        'amara.SubtitleEditor.subtitles.directives',
        'amara.SubtitleEditor.subtitles.filters',
        'amara.SubtitleEditor.subtitles.models',
        'amara.SubtitleEditor.subtitles.services',
        'amara.SubtitleEditor.timeline.controllers',
        'amara.SubtitleEditor.timeline.directives',
        'amara.SubtitleEditor.video.controllers',
        'amara.SubtitleEditor.video.directives',
        'amara.SubtitleEditor.video.services',
        'ngCookies'
    ]);

    // instead of using {{ }} for variables, use [[ ]]
    // so as to avoid conflict with django templating
    module.config(function($interpolateProvider) {
        $interpolateProvider.startSymbol('[[');
        $interpolateProvider.endSymbol(']]');
    });

    module.constant('MIN_DURATION', 250); // 0.25 seconds
    module.constant('DEFAULT_DURATION', 4000); // 4 seconds

    module.controller("AppController", function($scope, $controller,
            EditorData, Workflow) {

        $controller('AppControllerSubtitles', {$scope: $scope});
        $controller('AppControllerLocking', {$scope: $scope});
        $controller('AppControllerEvents', {$scope: $scope});

        $scope.videoId = EditorData.video.id;
        $scope.canSync = EditorData.canSync;
        $scope.canAddAndRemove = EditorData.canAddAndRemove;
        $scope.scrollingSynced = true;
        $scope.workflow = new Workflow($scope.workingSubtitles.subtitleList);
        $scope.timelineShown = !($scope.workflow.stage == 'type');
        $scope.toggleScrollingSynced = function() {
            $scope.scrollingSynced = !$scope.scrollingSynced;
        }
        $scope.toggleTimelineShown = function() {
            $scope.timelineShown = !$scope.timelineShown
        }
        // This is to experiement, will disapear soon
        setInterval(function(){
             var newHeaderSize = 48 + Math.max($('div.subtitles.reference .content').height(),
                                         $('div.subtitles.working .content').height());
            $('div.subtitles.reference .content').css('min-height', '' + newHeaderSize + 'px');
            $('div.subtitles.working .content').css('min-height', '' + newHeaderSize + 'px');
        },1000);

        $scope.adjustReferenceSize = function() {
            // Adjusts header size and then ref size so that refs and working look well in parallell
/*
            var newHeaderSize = Math.max($('div.subtitles.reference .content').height(),
                                         $('div.subtitles.working .content').height());
            $('div.subtitles.reference .content').height(newHeaderSize);
            $('div.subtitles.working .content').height(newHeaderSize);
*/

            if($scope.referenceSubtitles.subtitleList.length() > 0 && ($scope.referenceSubtitles.subtitleList.length() == $scope.workingSubtitles.subtitleList.length())) {
                var $reference = $('div.subtitles.reference').first();
                var $working = $('div.subtitles.working').first();
                if($reference.height() < $working.height())
                    $reference.last().height($reference.last().height() + $working.height() - $reference.height() );
            }
        }
	/*
	 * Might not be the right location
	 * TODO: move this to the proper place (probably the SubtitleList
	 * model.
	 */
        $scope.copyTimingOver = function() {
            var nextWorkingSubtitle = $scope.workingSubtitles.subtitleList.firstSubtitle();
            var nextReferenceSubtitle = $scope.referenceSubtitles.subtitleList.firstSubtitle();
            while (nextWorkingSubtitle && nextReferenceSubtitle) {
                $scope.workingSubtitles.subtitleList.updateSubtitleTime(nextWorkingSubtitle,
                                                                        nextReferenceSubtitle.startTime,
                                                                        nextReferenceSubtitle.endTime);
                $scope.workingSubtitles.subtitleList.updateSubtitleParagraph(nextWorkingSubtitle,
                                                                             $scope.referenceSubtitles.subtitleList.getSubtitleParagraph(nextReferenceSubtitle));
                nextWorkingSubtitle = $scope.workingSubtitles.subtitleList.nextSubtitle(nextWorkingSubtitle);
                nextReferenceSubtitle = $scope.referenceSubtitles.subtitleList.nextSubtitle(nextReferenceSubtitle);
            }
            while (nextWorkingSubtitle) {
                $scope.workingSubtitles.subtitleList.updateSubtitleTime(nextWorkingSubtitle, -1, -1);
                $scope.workingSubtitles.subtitleList.updateSubtitleParagraph(nextWorkingSubtitle, false);
                nextWorkingSubtitle = $scope.workingSubtitles.subtitleList.nextSubtitle(nextWorkingSubtitle);
            }
            // Sent no matter anything has changed or not, ideally we'd only emit
            // that if anything changed
            $scope.$root.$emit('work-done');
	}
        $scope.copyTimingEnabled = function() {
            return ($scope.workingSubtitles.subtitleList.length() > 0 &&
                     $scope.referenceSubtitles.subtitleList.syncedCount > 0)
        }
        $scope.timeline = {
            shownSubtitle: null,
            currentTime: null,
            duration: null,
        };
        // Hide the loading modal after we are done with bootstrapping
        // everything
        $scope.$evalAsync(function() {
            $scope.$root.$emit('hide-modal');
        });
    });

    /* AppController is large, so we split it into several components to
     * keep things a bit cleaner.  Each controller runs on the same scope.
     */

    /*
     * FIXME: this can probably be moved to a service to keep the app module
     * lean and mean.
     */
    module.controller("AppControllerLocking", function($scope, $timeout,
                EditorData, LockService) {
        var secondsUntilClosing = 120;
        var regainLockTimer;

        $scope.minutesIdle = 0;

        function releaseLock() {
            LockService.releaseLock($scope.videoId,
                    EditorData.editingVersion.languageCode);
        }

        function regainLock() {
            return LockService.regainLock($scope.videoId,
                    EditorData.editingVersion.languageCode);
        }

        function startUserIdleTimer() {
            var userIdleTimeout = function() {

                $scope.minutesIdle++;

                if ($scope.minutesIdle >= USER_IDLE_MINUTES) {
                    showIdleModal();
                    $timeout.cancel(regainLockTimer);
                } else {
                    $timeout(userIdleTimeout, 60 * 1000);
                }
            };

            $timeout(userIdleTimeout, 60 * 1000);
        }
        function startRegainLockTimer() {
            var regainLockTimeout = function() {
                regainLock();
                regainLockTimer = $timeout(regainLockTimeout, 15 * 1000);
            };

            regainLockTimer = $timeout(regainLockTimeout, 15 * 1000);

        }

        function regainLockAfterIdle() {
            regainLock().then(function onSuccess(response) {
                if (response.data.ok) {
                    $scope.minutesIdle = 0;
                    $scope.$root.$emit('hide-modal');
                    startRegainLockTimer();
                    startUserIdleTimer();
                } else {
                    window.alert("Sorry, could not restart your session.");
                    window.location = '/videos/' + $scope.videoId + "/";
                }
            }, function onError() {
                window.alert("Sorry, could not restart your session.");
                window.location = '/videos/' + $scope.videoId + "/";
            });
        }
        function showIdleModal() {

            var heading = "Warning: you've been idle for more than " + USER_IDLE_MINUTES + " minutes. " +
                "To ensure no work is lost we will close your session in ";

            var closeSessionTimeout;

            var closeSession = function() {

                secondsUntilClosing--;

                if (secondsUntilClosing <= 0) {

                    releaseLock();

                    $scope.$root.$emit("show-modal", {
                        heading: 'Your session has ended. You can try to resume, close the editor, or download your subtitles',
                        buttons: [
                            {'text': 'Try to resume work', 'class': 'yes', 'fn': function() {
                                // TODO: Remove this duplication from below.
                                if (closeSessionTimeout) {
                                    $timeout.cancel(closeSessionTimeout);
                                }

                                regainLockAfterIdle();
                            }},
                            {'text': 'Download subtitles', 'class': 'no', 'fn': function() {
                                $scope.$root.$emit('show-modal-download');
                            }},
                            {'text': 'Close editor', 'class': 'no', 'fn': function() {
                                window.location = '/videos/' + $scope.videoId + "/";
                            }}
                        ]
                    });

                } else {

                    $scope.$root.$emit('change-modal-heading', heading + secondsUntilClosing + " seconds.");
                    closeSessionTimeout = $timeout(closeSession, 1000);

                }
            };

            $scope.$root.$emit("show-modal", {
                heading: heading + secondsUntilClosing + " seconds.",
                buttons: [
                    {'text': 'Try to resume work', 'class': 'yes', 'fn': function() {
                        if (closeSessionTimeout) {
                            $timeout.cancel(closeSessionTimeout);
                        }

                        regainLockAfterIdle();
                    }}
                ]
            });

            closeSessionTimeout = $timeout(closeSession, 1000);
        }

        startUserIdleTimer();
        startRegainLockTimer();

        window.onunload = function() {
            releaseLock();
        }
    });

    module.controller("AppControllerEvents", function($scope, VideoPlayer) {
        function insertAndEditSubtitle() {
            var sub = $scope.workingSubtitles.subtitleList.insertSubtitleBefore(null);
            $scope.currentEdit.start(sub);
        }

        $scope.handleAppKeyDown = function(evt) {
            // Reset the lock timer.
            $scope.minutesIdle = 0;

            // Shortcuts that should work while editing a subtitle
            if ((evt.keyCode === 32 && evt.shiftKey) ||
                evt.keyCode == 9) {
                // Shift+Space or Tab: toggle play / pause.
                evt.preventDefault();
                evt.stopPropagation();
                VideoPlayer.togglePlay();
            } else if (evt.keyCode === 188 && evt.shiftKey && evt.ctrlKey) {
                // Control+Shift+Comma, go back 4 seconds
                VideoPlayer.seek(VideoPlayer.currentTime() - 4000);
            } else if (evt.keyCode === 190 && evt.shiftKey && evt.ctrlKey) {
                // Control+Shift+Period, go forward 4 seconds
                VideoPlayer.seek(VideoPlayer.currentTime() + 4000);
            } else if(evt.target.type == 'textarea') {
                return;
            }
            // Shortcuts that should be disabled while editing a subtitle
            else if(evt.keyCode == 40) {
                // Down arrow, set the start time of the first
                // unsynced sub
                if($scope.timelineShown) {
                    $scope.$root.$emit("sync-next-start-time");
                    evt.preventDefault();
                    evt.stopPropagation();
                }
            } else if(evt.keyCode == 38) {
                // Up arrow, set the end time of the first
                // unsynced sub
                if($scope.timelineShown) {
                    $scope.$root.$emit("sync-next-end-time");
                    evt.preventDefault();
                    evt.stopPropagation();
                }
            } else if(evt.keyCode == 13) {
                if(!$scope.timelineShown) {
                    insertAndEditSubtitle();
                    evt.preventDefault();
                }

            }
        };

        $scope.handleAppMouseMove = function(evt) {
            // Reset the lock timer.
            $scope.minutesIdle = 0;
        };
    });

    module.controller("AppControllerSubtitles", function($scope, EditorData,
                SubtitleStorage, CurrentEditManager, SubtitleVersionManager) {
        var video = EditorData.video;
        $scope.currentEdit = new CurrentEditManager();
        $scope.workingSubtitles = new SubtitleVersionManager(
            video, SubtitleStorage);
        $scope.referenceSubtitles = new SubtitleVersionManager(
            video, SubtitleStorage);
        var editingVersion = EditorData.editingVersion;

        if(editingVersion.versionNumber) {
            $scope.workingSubtitles.getSubtitles(editingVersion.languageCode,
                    editingVersion.versionNumber);
        } else {
            $scope.workingSubtitles.initEmptySubtitles(
                    editingVersion.languageCode, EditorData.baseLanguage);
        }

        $scope.saveSubtitles = function(markComplete) {
            return SubtitleStorage.saveSubtitles(
                    $scope.videoId,
                    $scope.workingSubtitles.language.code,
                    $scope.workingSubtitles.subtitleList.toXMLString(),
                    $scope.workingSubtitles.title,
                    $scope.workingSubtitles.description,
                    $scope.workingSubtitles.metadata,
                    markComplete);
        };
        function watchSubtitleAttributes(newValue, oldValue) {
            if(newValue != oldValue) {
                $scope.$root.$emit('work-done');
            }
        }
        $scope.$watch('workingSubtitles.title', watchSubtitleAttributes);
        $scope.$watch('workingSubtitles.description', watchSubtitleAttributes);
        $scope.$watch('workingSubtitles.metadata', watchSubtitleAttributes,
                true);
    });

}).call(this);
<commit_msg>Finally a more proper fix for issue 1099<commit_after>// Amara, universalsubtitles.org
//
// Copyright (C) 2013 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

var angular = angular || null;

(function() {

    var module = angular.module('amara.SubtitleEditor', [
        'amara.SubtitleEditor.collab',
        'amara.SubtitleEditor.help',
        'amara.SubtitleEditor.modal',
        'amara.SubtitleEditor.dom',
        'amara.SubtitleEditor.lock',
        'amara.SubtitleEditor.workflow',
        'amara.SubtitleEditor.subtitles.controllers',
        'amara.SubtitleEditor.subtitles.directives',
        'amara.SubtitleEditor.subtitles.filters',
        'amara.SubtitleEditor.subtitles.models',
        'amara.SubtitleEditor.subtitles.services',
        'amara.SubtitleEditor.timeline.controllers',
        'amara.SubtitleEditor.timeline.directives',
        'amara.SubtitleEditor.video.controllers',
        'amara.SubtitleEditor.video.directives',
        'amara.SubtitleEditor.video.services',
        'ngCookies'
    ]);

    // instead of using {{ }} for variables, use [[ ]]
    // so as to avoid conflict with django templating
    module.config(function($interpolateProvider) {
        $interpolateProvider.startSymbol('[[');
        $interpolateProvider.endSymbol(']]');
    });

    module.constant('MIN_DURATION', 250); // 0.25 seconds
    module.constant('DEFAULT_DURATION', 4000); // 4 seconds

    module.controller("AppController", function($scope, $controller,
            EditorData, Workflow) {

        $controller('AppControllerSubtitles', {$scope: $scope});
        $controller('AppControllerLocking', {$scope: $scope});
        $controller('AppControllerEvents', {$scope: $scope});

        $scope.videoId = EditorData.video.id;
        $scope.canSync = EditorData.canSync;
        $scope.canAddAndRemove = EditorData.canAddAndRemove;
        $scope.scrollingSynced = true;
        $scope.workflow = new Workflow($scope.workingSubtitles.subtitleList);
        $scope.timelineShown = !($scope.workflow.stage == 'type');
        $scope.toggleScrollingSynced = function() {
            $scope.scrollingSynced = !$scope.scrollingSynced;
        }
        $scope.toggleTimelineShown = function() {
            $scope.timelineShown = !$scope.timelineShown
        }
        $scope.keepHeaderSizeSync = function() {
            var newHeaderSize = Math.max($('div.subtitles.reference .content').outerHeight(),
                                         $('div.subtitles.working .content').outerHeight());
            $('div.subtitles.reference .content').css('min-height', '' + newHeaderSize + 'px');
            $('div.subtitles.working .content').css('min-height', '' + newHeaderSize + 'px');
        };
        // TODO: what is the angularjs way to bind functions to DOM events?
        $( "div.subtitles .content" ).change($scope.keepHeaderSizeSync);
        $scope.adjustReferenceSize = function() {
            $scope.keepHeaderSizeSync();
            if($scope.referenceSubtitles.subtitleList.length() > 0 && ($scope.referenceSubtitles.subtitleList.length() == $scope.workingSubtitles.subtitleList.length())) {
                var $reference = $('div.subtitles.reference').first();
                var $working = $('div.subtitles.working').first();
                if($reference.height() < $working.height())
                    $reference.last().height($reference.last().height() + $working.height() - $reference.height() );
            }
        }
	/*
	 * Might not be the right location
	 * TODO: move this to the proper place (probably the SubtitleList
	 * model.
	 */
        $scope.copyTimingOver = function() {
            var nextWorkingSubtitle = $scope.workingSubtitles.subtitleList.firstSubtitle();
            var nextReferenceSubtitle = $scope.referenceSubtitles.subtitleList.firstSubtitle();
            while (nextWorkingSubtitle && nextReferenceSubtitle) {
                $scope.workingSubtitles.subtitleList.updateSubtitleTime(nextWorkingSubtitle,
                                                                        nextReferenceSubtitle.startTime,
                                                                        nextReferenceSubtitle.endTime);
                $scope.workingSubtitles.subtitleList.updateSubtitleParagraph(nextWorkingSubtitle,
                                                                             $scope.referenceSubtitles.subtitleList.getSubtitleParagraph(nextReferenceSubtitle));
                nextWorkingSubtitle = $scope.workingSubtitles.subtitleList.nextSubtitle(nextWorkingSubtitle);
                nextReferenceSubtitle = $scope.referenceSubtitles.subtitleList.nextSubtitle(nextReferenceSubtitle);
            }
            while (nextWorkingSubtitle) {
                $scope.workingSubtitles.subtitleList.updateSubtitleTime(nextWorkingSubtitle, -1, -1);
                $scope.workingSubtitles.subtitleList.updateSubtitleParagraph(nextWorkingSubtitle, false);
                nextWorkingSubtitle = $scope.workingSubtitles.subtitleList.nextSubtitle(nextWorkingSubtitle);
            }
            // Sent no matter anything has changed or not, ideally we'd only emit
            // that if anything changed
            $scope.$root.$emit('work-done');
	}
        $scope.copyTimingEnabled = function() {
            return ($scope.workingSubtitles.subtitleList.length() > 0 &&
                     $scope.referenceSubtitles.subtitleList.syncedCount > 0)
        }
        $scope.timeline = {
            shownSubtitle: null,
            currentTime: null,
            duration: null,
        };
        // Hide the loading modal after we are done with bootstrapping
        // everything
        $scope.$evalAsync(function() {
            $scope.$root.$emit('hide-modal');
        });
    });

    /* AppController is large, so we split it into several components to
     * keep things a bit cleaner.  Each controller runs on the same scope.
     */

    /*
     * FIXME: this can probably be moved to a service to keep the app module
     * lean and mean.
     */
    module.controller("AppControllerLocking", function($scope, $timeout,
                EditorData, LockService) {
        var secondsUntilClosing = 120;
        var regainLockTimer;

        $scope.minutesIdle = 0;

        function releaseLock() {
            LockService.releaseLock($scope.videoId,
                    EditorData.editingVersion.languageCode);
        }

        function regainLock() {
            return LockService.regainLock($scope.videoId,
                    EditorData.editingVersion.languageCode);
        }

        function startUserIdleTimer() {
            var userIdleTimeout = function() {

                $scope.minutesIdle++;

                if ($scope.minutesIdle >= USER_IDLE_MINUTES) {
                    showIdleModal();
                    $timeout.cancel(regainLockTimer);
                } else {
                    $timeout(userIdleTimeout, 60 * 1000);
                }
            };

            $timeout(userIdleTimeout, 60 * 1000);
        }
        function startRegainLockTimer() {
            var regainLockTimeout = function() {
                regainLock();
                regainLockTimer = $timeout(regainLockTimeout, 15 * 1000);
            };

            regainLockTimer = $timeout(regainLockTimeout, 15 * 1000);

        }

        function regainLockAfterIdle() {
            regainLock().then(function onSuccess(response) {
                if (response.data.ok) {
                    $scope.minutesIdle = 0;
                    $scope.$root.$emit('hide-modal');
                    startRegainLockTimer();
                    startUserIdleTimer();
                } else {
                    window.alert("Sorry, could not restart your session.");
                    window.location = '/videos/' + $scope.videoId + "/";
                }
            }, function onError() {
                window.alert("Sorry, could not restart your session.");
                window.location = '/videos/' + $scope.videoId + "/";
            });
        }
        function showIdleModal() {

            var heading = "Warning: you've been idle for more than " + USER_IDLE_MINUTES + " minutes. " +
                "To ensure no work is lost we will close your session in ";

            var closeSessionTimeout;

            var closeSession = function() {

                secondsUntilClosing--;

                if (secondsUntilClosing <= 0) {

                    releaseLock();

                    $scope.$root.$emit("show-modal", {
                        heading: 'Your session has ended. You can try to resume, close the editor, or download your subtitles',
                        buttons: [
                            {'text': 'Try to resume work', 'class': 'yes', 'fn': function() {
                                // TODO: Remove this duplication from below.
                                if (closeSessionTimeout) {
                                    $timeout.cancel(closeSessionTimeout);
                                }

                                regainLockAfterIdle();
                            }},
                            {'text': 'Download subtitles', 'class': 'no', 'fn': function() {
                                $scope.$root.$emit('show-modal-download');
                            }},
                            {'text': 'Close editor', 'class': 'no', 'fn': function() {
                                window.location = '/videos/' + $scope.videoId + "/";
                            }}
                        ]
                    });

                } else {

                    $scope.$root.$emit('change-modal-heading', heading + secondsUntilClosing + " seconds.");
                    closeSessionTimeout = $timeout(closeSession, 1000);

                }
            };

            $scope.$root.$emit("show-modal", {
                heading: heading + secondsUntilClosing + " seconds.",
                buttons: [
                    {'text': 'Try to resume work', 'class': 'yes', 'fn': function() {
                        if (closeSessionTimeout) {
                            $timeout.cancel(closeSessionTimeout);
                        }

                        regainLockAfterIdle();
                    }}
                ]
            });

            closeSessionTimeout = $timeout(closeSession, 1000);
        }

        startUserIdleTimer();
        startRegainLockTimer();

        window.onunload = function() {
            releaseLock();
        }
    });

    module.controller("AppControllerEvents", function($scope, VideoPlayer) {
        function insertAndEditSubtitle() {
            var sub = $scope.workingSubtitles.subtitleList.insertSubtitleBefore(null);
            $scope.currentEdit.start(sub);
        }

        $scope.handleAppKeyDown = function(evt) {
            // Reset the lock timer.
            $scope.minutesIdle = 0;

            // Shortcuts that should work while editing a subtitle
            if ((evt.keyCode === 32 && evt.shiftKey) ||
                evt.keyCode == 9) {
                // Shift+Space or Tab: toggle play / pause.
                evt.preventDefault();
                evt.stopPropagation();
                VideoPlayer.togglePlay();
            } else if (evt.keyCode === 188 && evt.shiftKey && evt.ctrlKey) {
                // Control+Shift+Comma, go back 4 seconds
                VideoPlayer.seek(VideoPlayer.currentTime() - 4000);
            } else if (evt.keyCode === 190 && evt.shiftKey && evt.ctrlKey) {
                // Control+Shift+Period, go forward 4 seconds
                VideoPlayer.seek(VideoPlayer.currentTime() + 4000);
            } else if(evt.target.type == 'textarea') {
                return;
            }
            // Shortcuts that should be disabled while editing a subtitle
            else if(evt.keyCode == 40) {
                // Down arrow, set the start time of the first
                // unsynced sub
                if($scope.timelineShown) {
                    $scope.$root.$emit("sync-next-start-time");
                    evt.preventDefault();
                    evt.stopPropagation();
                }
            } else if(evt.keyCode == 38) {
                // Up arrow, set the end time of the first
                // unsynced sub
                if($scope.timelineShown) {
                    $scope.$root.$emit("sync-next-end-time");
                    evt.preventDefault();
                    evt.stopPropagation();
                }
            } else if(evt.keyCode == 13) {
                if(!$scope.timelineShown) {
                    insertAndEditSubtitle();
                    evt.preventDefault();
                }

            }
        };

        $scope.handleAppMouseMove = function(evt) {
            // Reset the lock timer.
            $scope.minutesIdle = 0;
        };
    });

    module.controller("AppControllerSubtitles", function($scope, EditorData,
                SubtitleStorage, CurrentEditManager, SubtitleVersionManager) {
        var video = EditorData.video;
        $scope.currentEdit = new CurrentEditManager();
        $scope.workingSubtitles = new SubtitleVersionManager(
            video, SubtitleStorage);
        $scope.referenceSubtitles = new SubtitleVersionManager(
            video, SubtitleStorage);
        var editingVersion = EditorData.editingVersion;

        if(editingVersion.versionNumber) {
            $scope.workingSubtitles.getSubtitles(editingVersion.languageCode,
                    editingVersion.versionNumber);
        } else {
            $scope.workingSubtitles.initEmptySubtitles(
                    editingVersion.languageCode, EditorData.baseLanguage);
        }

        $scope.saveSubtitles = function(markComplete) {
            return SubtitleStorage.saveSubtitles(
                    $scope.videoId,
                    $scope.workingSubtitles.language.code,
                    $scope.workingSubtitles.subtitleList.toXMLString(),
                    $scope.workingSubtitles.title,
                    $scope.workingSubtitles.description,
                    $scope.workingSubtitles.metadata,
                    markComplete);
        };
        function watchSubtitleAttributes(newValue, oldValue) {
            if(newValue != oldValue) {
                $scope.$root.$emit('work-done');
            }
        }
        $scope.$watch('workingSubtitles.title', watchSubtitleAttributes);
        $scope.$watch('workingSubtitles.description', watchSubtitleAttributes);
        $scope.$watch('workingSubtitles.metadata', watchSubtitleAttributes,
                true);
    });

}).call(this);
<|endoftext|>


<commit_before>var chai = require('chai'),
    assert = chai.assert,
    Reflux = require('../src'),
    Q = require('q'),
    sinon = require('sinon'),
    util = require('util');

chai.use(require('chai-as-promised'));

describe("using the publisher methods mixin",function(){
    var pub = Reflux.PublisherMethods;

    describe("the promise method",function(){

        describe("when the promise completes",function(){
            var deferred = Q.defer(),
                promise = deferred.promise,
                context = {
                    children:['completed','failed'],
                    completed:sinon.spy(),
                    failed:sinon.spy()
                },
                result = pub.promise.call(context,promise);

            deferred.resolve('foo');

            it("should not return a value",function(){
                assert.equal(result, undefined);
            });

            it("should call the completed child trigger",function(){
                var args = context.completed.firstCall.args;
                assert.deepEqual(args, ["foo"]);
            });

            it("should not call the failed child trigger",function(){
                assert.equal(context.failed.callCount, 0);
            });
        });

        describe("when the promise fails",function(){
            var deferred = Q.defer(),
                promise = deferred.promise,
                context = {
                    children:['completed','failed'],
                    completed:sinon.spy(),
                    failed:sinon.spy()
                },
                result = pub.promise.call(context,promise);

            deferred.reject('bar');

            it("should not return a value",function(){
                assert.equal(result, undefined);
            });

            it("should call the failed child trigger",function(){
                var args = context.failed.firstCall.args;
                assert.deepEqual(args, ["bar"]);
            });

            it("should not the completed child trigger",function(){
                assert.equal(context.completed.callCount, 0);
            });
        });
    });

    describe("the listen method",function(){
        var emitter = {
                addListener:sinon.spy(),
                removeListener:sinon.spy()
            },
            context = {
                emitter: emitter,
                eventLabel: "LABEL",
            },
            callback = sinon.spy(),
            cbcontext = {foo:"BAR"},
            result = pub.listen.call(context,callback,cbcontext);

        it("should call addListener correctly",function(){
            var args = emitter.addListener.firstCall.args;
            assert.equal(args[0],context.eventLabel);
            assert.isFunction(args[1]);
            args[1](["ARG1","ARG2"]);
            assert.deepEqual(callback.firstCall.args,["ARG1","ARG2"]);
            assert.equal(callback.firstCall.thisValue,cbcontext);
        });

        describe("the returned value",function(){

            it("should be a function",function(){
                assert.isFunction(result);
            });

            it("should remove the listener correctly",function(){
                result();
                assert.deepEqual(emitter.removeListener.firstCall.args,[context.eventLabel,emitter.addListener.firstCall.args[1]]);
            });
        });
    });

    describe("the trigger method",function(){

        describe("when shouldEmit returns true",function(){

            describe("when preEmit returns undefined",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.spy(),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.call(context,"FOO","BAR");

                it("should call preEmit correctly",function(){
                    assert.deepEqual(context.preEmit.firstCall.args,["FOO","BAR"]);
                });

                it("should call shouldEmit correctly",function(){
                    assert.deepEqual(context.shouldEmit.firstCall.args,["FOO","BAR"]);
                });

                it("should call emit on the emitter",function(){
                    // args are weird because it is an arguments object
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1][0],"FOO");
                    assert.deepEqual(args[1][1],"BAR");
                });
            });

            describe("when preEmit returns an array of new args",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newargs = ["foo","bar"],
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newargs),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the changed args",function(){
                    assert.deepEqual(context.shouldEmit.firstCall.args,newargs);
                });

                it("should call emit on the emitter with the changed args",function(){
                    // args are weird because it is an arguments object
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],newargs);
                });
            });

            describe("when preEmit returns the arguments array",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    context = {
                        eventLabel: "LABEL",
                        preEmit:function(){return arguments;},
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should correctly call shouldEmit as if we returned an array",function(){
                    assert.deepEqual(oldargs,context.shouldEmit.firstCall.args);
                });

                it("should call emit the same way too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1][0],oldargs[0]);
                    assert.deepEqual(args[1][1],oldargs[1]);
                });
            });

            describe("when preEmit returns a string",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = "I SHOULD BE USED AS A SINGLE ARG",
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the string",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with the string too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });

            describe("when preEmit returns a number",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = 12345,
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the number",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with the number too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });

            describe("when preEmit returns false",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = false,
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with false",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with false too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });

            describe("when preEmit returns an object",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = {a:"foo",b:"bar"},
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the object",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with the object too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });

            describe("when preEmit returns a function",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = function(foo,bar){console.log(foo,bar);},
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the function",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with the function too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });
        });

        describe("when shouldEmit returns false",function(){
            var emitter = {
                    emit: sinon.spy()
                },
                context = {
                    preEmit:sinon.spy(),
                    shouldEmit:sinon.stub().returns(false),
                    emitter: emitter
                };
            pub.trigger.call(context,"FOO","BAR");

            it("should not emit anything",function(){
                assert.equal(emitter.emit.callCount,0);
            });
        });
    });

    describe("the triggerPromise method",function(){
        it("should require completed & failed actions", function() {
            var contexts = [
                { children: [] },
                { children: ['completed'] },
                { children: ['failed'] },
            ];

            contexts.forEach(function(context){
                try{
                    pub.triggerPromise.call(context);
                    assert(false);
                }catch(e){
                    assert.equal(e.message, 'Publisher must have "completed" and "failed" child publishers');
                }
            });
        });

        it("should return a promise",function(){
            var context = {
                children:['completed','failed'],
                completed:sinon.spy(),
                failed:sinon.spy()
            };

            var promise = pub.triggerPromise.call(context);

            assert(promise instanceof Promise);
        });

        it("should resolve when completed",function(){
            var action = Reflux.createAction({ asyncResult: true });

            Reflux.createStore({
                init: function() {
                    this.listenTo(action, this.onAction);
                },
                onAction: function(verb, noun) {
                    setTimeout(function() {
                        action.completed(util.format('%s %s completed', verb, noun));
                    }, 10);
                },
            });

            var promise = action.triggerPromise('do', 'something');

            return assert.becomes(promise, 'do something completed');
        });

        it("should reject when failed",function(){
            var action = Reflux.createAction({ asyncResult: true });

            Reflux.createStore({
                init: function() {
                    this.listenTo(action, this.onAction);
                },
                onAction: function(verb, noun) {
                    setTimeout(function() {
                        action.failed(util.format('%s %s faiiled', verb, noun));
                    }, 10);
                },
            });

            var promise = action.triggerPromise('do', 'something');

            return assert.isRejected(promise, 'do something failed');
        });
    });
});
<commit_msg>Don't guess, test triggerPromise<commit_after>var chai = require('chai'),
    assert = chai.assert,
    Reflux = require('../src'),
    Q = require('q'),
    sinon = require('sinon'),
    util = require('util');

chai.use(require('chai-as-promised'));

describe("using the publisher methods mixin",function(){
    var pub = Reflux.PublisherMethods;

    describe("the promise method",function(){

        describe("when the promise completes",function(){
            var deferred = Q.defer(),
                promise = deferred.promise,
                context = {
                    children:['completed','failed'],
                    completed:sinon.spy(),
                    failed:sinon.spy()
                },
                result = pub.promise.call(context,promise);

            deferred.resolve('foo');

            it("should not return a value",function(){
                assert.equal(result, undefined);
            });

            it("should call the completed child trigger",function(){
                var args = context.completed.firstCall.args;
                assert.deepEqual(args, ["foo"]);
            });

            it("should not call the failed child trigger",function(){
                assert.equal(context.failed.callCount, 0);
            });
        });

        describe("when the promise fails",function(){
            var deferred = Q.defer(),
                promise = deferred.promise,
                context = {
                    children:['completed','failed'],
                    completed:sinon.spy(),
                    failed:sinon.spy()
                },
                result = pub.promise.call(context,promise);

            deferred.reject('bar');

            it("should not return a value",function(){
                assert.equal(result, undefined);
            });

            it("should call the failed child trigger",function(){
                var args = context.failed.firstCall.args;
                assert.deepEqual(args, ["bar"]);
            });

            it("should not the completed child trigger",function(){
                assert.equal(context.completed.callCount, 0);
            });
        });
    });

    describe("the listen method",function(){
        var emitter = {
                addListener:sinon.spy(),
                removeListener:sinon.spy()
            },
            context = {
                emitter: emitter,
                eventLabel: "LABEL",
            },
            callback = sinon.spy(),
            cbcontext = {foo:"BAR"},
            result = pub.listen.call(context,callback,cbcontext);

        it("should call addListener correctly",function(){
            var args = emitter.addListener.firstCall.args;
            assert.equal(args[0],context.eventLabel);
            assert.isFunction(args[1]);
            args[1](["ARG1","ARG2"]);
            assert.deepEqual(callback.firstCall.args,["ARG1","ARG2"]);
            assert.equal(callback.firstCall.thisValue,cbcontext);
        });

        describe("the returned value",function(){

            it("should be a function",function(){
                assert.isFunction(result);
            });

            it("should remove the listener correctly",function(){
                result();
                assert.deepEqual(emitter.removeListener.firstCall.args,[context.eventLabel,emitter.addListener.firstCall.args[1]]);
            });
        });
    });

    describe("the trigger method",function(){

        describe("when shouldEmit returns true",function(){

            describe("when preEmit returns undefined",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.spy(),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.call(context,"FOO","BAR");

                it("should call preEmit correctly",function(){
                    assert.deepEqual(context.preEmit.firstCall.args,["FOO","BAR"]);
                });

                it("should call shouldEmit correctly",function(){
                    assert.deepEqual(context.shouldEmit.firstCall.args,["FOO","BAR"]);
                });

                it("should call emit on the emitter",function(){
                    // args are weird because it is an arguments object
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1][0],"FOO");
                    assert.deepEqual(args[1][1],"BAR");
                });
            });

            describe("when preEmit returns an array of new args",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newargs = ["foo","bar"],
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newargs),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the changed args",function(){
                    assert.deepEqual(context.shouldEmit.firstCall.args,newargs);
                });

                it("should call emit on the emitter with the changed args",function(){
                    // args are weird because it is an arguments object
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],newargs);
                });
            });

            describe("when preEmit returns the arguments array",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    context = {
                        eventLabel: "LABEL",
                        preEmit:function(){return arguments;},
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should correctly call shouldEmit as if we returned an array",function(){
                    assert.deepEqual(oldargs,context.shouldEmit.firstCall.args);
                });

                it("should call emit the same way too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1][0],oldargs[0]);
                    assert.deepEqual(args[1][1],oldargs[1]);
                });
            });

            describe("when preEmit returns a string",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = "I SHOULD BE USED AS A SINGLE ARG",
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the string",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with the string too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });

            describe("when preEmit returns a number",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = 12345,
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the number",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with the number too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });

            describe("when preEmit returns false",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = false,
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with false",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with false too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });

            describe("when preEmit returns an object",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = {a:"foo",b:"bar"},
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the object",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with the object too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });

            describe("when preEmit returns a function",function(){
                var emitter = {
                        emit: sinon.spy()
                    },
                    oldargs = ["what","ever"],
                    newarg = function(foo,bar){console.log(foo,bar);},
                    context = {
                        eventLabel: "LABEL",
                        preEmit:sinon.stub().returns(newarg),
                        shouldEmit:sinon.stub().returns(true),
                        emitter: emitter
                    };
                pub.trigger.apply(context,oldargs);

                it("should call shouldEmit with the function",function(){
                    assert.deepEqual([newarg],context.shouldEmit.firstCall.args);
                });

                it("should call emit with the function too",function(){
                    var args = emitter.emit.firstCall.args;
                    assert.deepEqual(args[0],"LABEL");
                    assert.deepEqual(args[1],[newarg]);
                });
            });
        });

        describe("when shouldEmit returns false",function(){
            var emitter = {
                    emit: sinon.spy()
                },
                context = {
                    preEmit:sinon.spy(),
                    shouldEmit:sinon.stub().returns(false),
                    emitter: emitter
                };
            pub.trigger.call(context,"FOO","BAR");

            it("should not emit anything",function(){
                assert.equal(emitter.emit.callCount,0);
            });
        });
    });

    describe("the triggerPromise method",function(){
        it("should require completed & failed actions", function() {
            var contexts = [
                { children: [] },
                { children: ['completed'] },
                { children: ['failed'] },
            ];

            contexts.forEach(function(context){
                try{
                    pub.triggerPromise.call(context);
                    assert(false);
                }catch(e){
                    assert.equal(e.message, 'Publisher must have "completed" and "failed" child publishers');
                }
            });
        });

        it("should return a promise",function(){
            var context = {
                children:['completed','failed'],
                completed:sinon.spy(),
                failed:sinon.spy()
            };

            var promise = pub.triggerPromise.call(context);

            assert(promise instanceof Promise);
        });

        it("should resolve when completed",function(){
            var action = Reflux.createAction({ asyncResult: true });

            Reflux.createStore({
                init: function() {
                    this.listenTo(action, this.onAction);
                },
                onAction: function(verb, noun) {
                    setTimeout(function() {
                        action.completed(util.format('%s %s completed', verb, noun));
                    }, 10);
                },
            });

            var promise = action.triggerPromise('do', 'something');

            return assert.becomes(promise, 'do something completed');
        });

        it("should reject when failed",function(){
            var action = Reflux.createAction({ asyncResult: true });

            Reflux.createStore({
                init: function() {
                    this.listenTo(action, this.onAction);
                },
                onAction: function(verb, noun) {
                    setTimeout(function() {
                        action.failed(util.format('%s %s faiiled', verb, noun));
                    }, 10);
                },
            });

            var promise = action.triggerPromise('do', 'something');

            return assert.isRejected(promise, 'do something failed');
        });

        it("should resolve with the promised result",function(){
            var makePancakes = Reflux.createAction({ asyncResult: true });
            makePancakes.listenAndPromise(function (flour, milk, egg) {
              return Q.promise(function(resolve) {
                setTimeout(function(){
                  resolve(flour + milk + egg);
                }, 200);
              });
            });

            makePancakes.triggerPromise(2,1,1);

            var morePancakes = makePancakes.triggerPromise(4,2,1);

            return assert.becomes(morePancakes, 7, 'became a different result');
        });
    });
});
<|endoftext|>


<commit_before>function Promise (executor) {

  const self = this;
  this.status = 'pending'; // promise当前的状态
  this.data = undefined; // promise的值
  this.onResolvedCallback = []; // promise resolve时的回调函数，可能有多个回调
  this.onRejectedCallback = []; // promise reject时的回调函数，可能有多个回调

  function resolve (value) {

    if (value instanceof Promise) {
      return value.then(resolve, reject);
    }

    // 保证异步调用执行
    setTimeout(function () {
      if (self.status === 'pending') {
        self.status = 'resolved';
        self.data = value;
        for (let i = 0; i < self.onResolvedCallback.length; i++) {
          self.onResolvedCallback[i](value)
        }
      }
    }, 0)

  }

  function reject (reason) {

    setTimeout(function () {
      if (self.status === 'pending') {
        self.status = 'rejected';
        self.data = reason;
        for (let i = 0; i < self.onRejectedCallback.length; i++) {
          self.onRejectedCallback[i](reason);
        }
      }
    }, 0)

  }

  try {
    executor(resolve, reject);
  } catch (e) {
    reject(e);
  }

}

function resolvePromise (promise2, x, resolve, reject) {
  let then,
    thenCalledOrThrow = false;

  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'));
  }

  if (x instanceof Promise) {
    if (x.status === 'pending') {
      x.then(function (v) {
        resolvePromise(promise2, v, resolve, reject)
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }

  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
    try {
      then = x.then;
      if (typeof then === 'function') {
        then.call(x, function rs (y) {
          if (thenCalledOrThrow) return;
          thenCalledOrThrow = true;
          return resolvePromise(promise2, y, resolve, reject);
        }, function rj (r) {
          if (thenCalledOrThrow) return;
          thenCalledOrThrow = true;
          return reject(r);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if (thenCalledOrThrow) return;
      thenCalledOrThrow = true;
      return reject(e);
    }
  } else {
    resolve(x);
  }
}

Promise.prototype.then = function (onResolved, onRejected) {
  const self = this;
  let promise2;

  // 判断onResolved, onRejected是否是function，不是则需要重置处理(做值的穿透处理)
  onResolved = typeof onResolved === 'function' ? onResolved : function (v) { return v; };
  onRejected = typeof onRejected === 'function' ? onRejected : function (r) { return r; };

  if (self.status === 'resolved') {

    promise2 = new Promise(function (resolve, reject) {

      setTimeout(function () {
        try {

          let x = onResolved(self.data);

          resolvePromise(promise2, x, resolve, reject);

        } catch (e) {

          reject(e);

        }
      })

    })

  } else if (self.status === 'rejected') {

    promise2 = new Promise(function (resolve, reject) {

      setTimeout(function () {

        try {

          let x = onRejected(self.data);

          resolvePromise(promise2, x, resolve, reject);

        } catch (e) {

          reject(e);

        }

      })

    })

  } else if (self.status === ' pending') {

    promise2 = new Promise(function (resolve, reject) {
      self.onResolvedCallback.push(function (value) {

        try {

          let x = onResolved(value);

          resolvePromise(promise2, x, resolve, reject);

        } catch (e) {

          reject(e);

        }

      });

      self.onRejectedCallback.push(function (reason) {

        try {

          let x = onRejected(reason);
          resolvePromise(promise2, x, resolve, reject);

        } catch (e) {

          reject(e);

        }

      })
    })

  }

  return promise2;
}

Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
}

// for test usage
Promise.deferred = Promise.defer = function () {
  let dfd = {};
  dfd.promise = new Promise(function (resolve, reject) {
    dfd.resolve = resolve;
    dfd.reject = reject;
  })
  return dfd;
}

module.exports = Promise;<commit_msg>pass the test<commit_after>function Promise (executor) {

  const self = this;
  this.status = 'pending'; // promise当前的状态
  this.data = undefined; // promise的值
  this.onResolvedCallback = []; // promise resolve时的回调函数，可能有多个回调
  this.onRejectedCallback = []; // promise reject时的回调函数，可能有多个回调

  function resolve (value) {

    if (value instanceof Promise) {
      return value.then(resolve, reject);
    }

    // 保证异步调用执行
    setTimeout(function () {
      if (self.status === 'pending') {
        self.status = 'resolved';
        self.data = value;
        for (let i = 0; i < self.onResolvedCallback.length; i++) {
          self.onResolvedCallback[i](value)
        }
      }
    }, 0)

  }

  function reject (reason) {

    setTimeout(function () {
      if (self.status === 'pending') {
        self.status = 'rejected';
        self.data = reason;
        for (let i = 0; i < self.onRejectedCallback.length; i++) {
          self.onRejectedCallback[i](reason);
        }
      }
    }, 0)

  }

  try {
    executor(resolve, reject);
  } catch (e) {
    reject(e);
  }

}

function resolvePromise (promise2, x, resolve, reject) {
  let then,
    thenCalledOrThrow = false;

  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'));
  }

  if (x instanceof Promise) {
    if (x.status === 'pending') {
      x.then(function (v) {
        resolvePromise(promise2, v, resolve, reject)
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }

  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
    try {
      then = x.then;
      if (typeof then === 'function') {
        then.call(x, function rs (y) {
          if (thenCalledOrThrow) return;
          thenCalledOrThrow = true;
          return resolvePromise(promise2, y, resolve, reject);
        }, function rj (r) {
          if (thenCalledOrThrow) return;
          thenCalledOrThrow = true;
          return reject(r);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if (thenCalledOrThrow) return;
      thenCalledOrThrow = true;
      return reject(e);
    }
  } else {
    resolve(x);
  }
}

Promise.prototype.then = function (onResolved, onRejected) {
  const self = this;
  let promise2;

  // 判断onResolved, onRejected是否是function，不是则需要重置处理(做值的穿透处理)
  onResolved = typeof onResolved === 'function' ? onResolved : function (v) { return v; };
  onRejected = typeof onRejected === 'function' ? onRejected : function (r) { throw r; };

  if (self.status === 'resolved') {

    return promise2 = new Promise(function (resolve, reject) {

      setTimeout(function () {
        try {

          let x = onResolved(self.data);

          resolvePromise(promise2, x, resolve, reject);

        } catch (e) {

          reject(e);

        }
      })

    })

  }

  if (self.status === 'rejected') {

    return promise2 = new Promise(function (resolve, reject) {

      setTimeout(function () {

        try {

          let x = onRejected(self.data);

          resolvePromise(promise2, x, resolve, reject);

        } catch (e) {

          reject(e);

        }

      })

    })

  }

  if (self.status === 'pending') {

    return promise2 = new Promise(function (resolve, reject) {
      self.onResolvedCallback.push(function (value) {

        try {

          let x = onResolved(value);

          resolvePromise(promise2, x, resolve, reject);

        } catch (e) {

          reject(e);

        }

      });

      self.onRejectedCallback.push(function (reason) {

        try {

          let x = onRejected(reason);
          resolvePromise(promise2, x, resolve, reject);

        } catch (e) {

          reject(e);

        }

      })
    })

  }

}

Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
}

// for test usage
Promise.deferred = Promise.defer = function () {
  let dfd = {};
  dfd.promise = new Promise(function (resolve, reject) {
    dfd.resolve = resolve;
    dfd.reject = reject;
  })
  return dfd;
}

module.exports = Promise;<|endoftext|>


<commit_before>// Copyright 2018-2020, University of Colorado Boulder

/**
 * QUnit tests for GrabDragInteraction
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

import Display from '../../../scenery/js/display/Display.js';
import Node from '../../../scenery/js/nodes/Node.js';
import Rectangle from '../../../scenery/js/nodes/Rectangle.js';
import GrabDragInteraction from './GrabDragInteraction.js';

// constants
const thingString = 'thing';

QUnit.module( 'GrabDragInteraction' );

QUnit.test( 'GrabDragInteraction defaults', assert => {

  assert.ok( true, 'first test' );

  const rootNode = new Node( { tagName: 'div' } );
  const display = new Display( rootNode ); // eslint-disable-line
  display.initializeEvents();
  document.body.appendChild( display.domElement );

  phet = phet || {}; // eslint-disable-line no-global-assign
  phet.joist = phet.joist || {};
  phet.joist.sim = phet.joist.sim || { utteranceQueue: display.utteranceQueue }; // stub utteranceQueue global

  const a = new Rectangle( 0, 0, 5, 5 );

  rootNode.addChild( a );

  const interaction = new GrabDragInteraction( a, {
    objectToGrabString: thingString
  } );

  const testDefaultGrabbable = () => {

    assert.ok( interaction.grabbable, 'default to grabbable' );
    assert.ok( a.tagName.toUpperCase() === 'BUTTON', 'grabbable defaults to button' );
    assert.ok( a.ariaRole === null, 'no role for grabbable' );
    assert.ok( a.ariaLabel === null, 'no aria-label for grabbable' );

    const aElement = a.accessibleInstances[ 0 ].peer.primarySibling;
    assert.ok( aElement.tagName === 'BUTTON', 'grabbable defaults to button html element.' );
  };

  testDefaultGrabbable();

  a.accessibleInstances[ 0 ].peer.primarySibling.click();

  const testDefaultDraggable = () => {

    assert.ok( !interaction.grabbable, 'should be draggable after click draggable' );
    assert.ok( a.tagName.toUpperCase() === 'DIV', 'draggable defaults to div' );
    assert.ok( a.ariaRole === 'application', 'draggable gets application role' );
    assert.ok( a.ariaLabel.indexOf( thingString ) > 0, 'ariaLabel should include thing string' );
    assert.ok( a.ariaLabel === a.innerContent, 'ariaLabel should include thing string' );

    const aElement = a.accessibleInstances[ 0 ].peer.primarySibling;
    assert.ok( aElement.tagName === 'DIV', 'draggable defaults to div html element.' );
    assert.ok( aElement.getAttribute( 'aria-roledescription' ) === a.ariaLabel, 'aria role description should be same as model label' );
    assert.ok( aElement.innerHTML === a.ariaLabel, 'element innerHTML should be same as model label' );
    assert.ok( aElement.getAttribute( 'aria-label' ) === a.ariaLabel, 'element innerHTML should be same as model label' );
  };

  testDefaultDraggable();

  a.accessibleInstances[ 0 ].peer.primarySibling.blur();

  testDefaultGrabbable();

  display.detachEvents();
} );<commit_msg>address some unit test issues identified by CT<commit_after>// Copyright 2018-2020, University of Colorado Boulder

/**
 * QUnit tests for GrabDragInteraction
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

import Display from '../../../scenery/js/display/Display.js';
import Node from '../../../scenery/js/nodes/Node.js';
import Rectangle from '../../../scenery/js/nodes/Rectangle.js';
import GrabDragInteraction from './GrabDragInteraction.js';

// constants
const thingString = 'thing';
const movableString = 'movable';

QUnit.module( 'GrabDragInteraction' );

QUnit.test( 'GrabDragInteraction defaults', assert => {

  assert.ok( true, 'first test' );

  const rootNode = new Node( { tagName: 'div' } );
  const display = new Display( rootNode ); // eslint-disable-line
  display.initializeEvents();
  document.body.appendChild( display.domElement );

  phet = phet || {}; // eslint-disable-line no-global-assign
  phet.joist = phet.joist || {};
  phet.joist.sim = phet.joist.sim || { utteranceQueue: display.utteranceQueue }; // stub utteranceQueue global

  // GrabDragInteraction requires a sim
  phet.joist.sim.supportsGestureDescription = phet.joist.sim.supportsGestureDescription || false;

  const a = new Rectangle( 0, 0, 5, 5 );

  rootNode.addChild( a );

  const interaction = new GrabDragInteraction( a, {
    objectToGrabString: thingString
  } );

  const testDefaultGrabbable = () => {

    assert.ok( interaction.grabbable, 'default to grabbable' );
    assert.ok( a.tagName.toUpperCase() === 'BUTTON', 'grabbable defaults to button' );
    assert.ok( a.ariaRole === null, 'no role for grabbable' );
    assert.ok( a.ariaLabel === null, 'no aria-label for grabbable' );

    const aElement = a.accessibleInstances[ 0 ].peer.primarySibling;
    assert.ok( aElement.tagName === 'BUTTON', 'grabbable defaults to button html element.' );
  };

  testDefaultGrabbable();

  a.accessibleInstances[ 0 ].peer.primarySibling.click();

  const testDefaultDraggable = () => {

    assert.ok( !interaction.grabbable, 'should be draggable after click draggable' );
    assert.ok( a.tagName.toUpperCase() === 'DIV', 'draggable defaults to div' );
    assert.ok( a.ariaRole === 'application', 'draggable gets application role' );
    assert.ok( a.ariaLabel.indexOf( thingString ) >= 0, 'ariaLabel should include thing string' );
    assert.ok( a.ariaLabel === a.innerContent, 'ariaLabel should include thing string' );

    const aElement = a.accessibleInstances[ 0 ].peer.primarySibling;
    assert.ok( aElement.tagName === 'DIV', 'draggable defaults to div html element.' );
    assert.ok( aElement.getAttribute( 'aria-roledescription' ) === movableString, 'aria role description should describe that it is movable by default' );
    assert.ok( aElement.innerHTML === a.ariaLabel, 'element innerHTML should be same as model label' );
    assert.ok( aElement.getAttribute( 'aria-label' ) === a.ariaLabel, 'element innerHTML should be same as model label' );
  };

  testDefaultDraggable();

  a.accessibleInstances[ 0 ].peer.primarySibling.blur();

  testDefaultGrabbable();

  display.detachEvents();
} );<|endoftext|>


<commit_before>'use strict';

import * as _ from 'lodash'
import * as React from 'react'
import * as humanize from 'humanize-plus'

import DraggableMixin from '../mixins/draggable'

var Course = React.createClass({
	render() {
		return React.DOM.article({className: 'course'},
			React.DOM.h1({className: 'title'}, this.props.info.title),
			React.DOM.span({className: 'details'},
				React.DOM.span({className: 'identifier'},
					React.DOM.span({className: 'department'}, this.props.info.depts.join('/')),
					' ',
					React.DOM.span({className: 'number'}, this.props.info.num),
					this.props.info.sect ? React.DOM.span({className: 'section'}, this.props.info.sect) : ''
				),
				React.DOM.span({className: 'professors'}, humanize.oxford(this.props.info.profs))
			)
		)
	}
})

export default Course
<commit_msg>Add an info button to Course<commit_after>'use strict';

import * as _ from 'lodash'
import * as React from 'react'
import * as humanize from 'humanize-plus'

var Course = React.createClass({
	render() {
		let title = this.props.info.type === 'Topic' ? this.props.info.name : this.props.info.title;

		let summary = React.DOM.article({className: 'course'},
			React.DOM.div({className: 'info-rows'},
				React.DOM.h1({className: 'title'}, title),
				React.DOM.span({className: 'details'},
					React.DOM.span({className: 'identifier'},
						React.DOM.span({className: 'department'}, this.props.info.dept),
						' ', React.DOM.span({className: 'number'}, this.props.info.num),
						this.props.info.sect ?
							React.DOM.span({className: 'section'}, this.props.info.sect) :
							''),
					React.DOM.span({className: 'professors'}, humanize.oxford(this.props.info.profs)))
			),
			React.DOM.div({
				className: 'info-button',
			})
		)

		return summary;
	}
})

export default Course
<|endoftext|>


<commit_before>// Copyright 2013-2020, University of Colorado Boulder

/**
 * This toggle button uses a boolean property and a trueNode and falseNode to display its content.
 */

import inherit from '../../../phet-core/js/inherit.js';
import merge from '../../../phet-core/js/merge.js';
import Tandem from '../../../tandem/js/Tandem.js';
import BooleanToggleNode from '../BooleanToggleNode.js';
import sun from '../sun.js';
import RectangularToggleButton from './RectangularToggleButton.js';

/**
 * @param {Node} trueNode
 * @param {Node} falseNode
 * @param {Property.<boolean>} booleanProperty
 * @param {Object} [options]
 * @constructor
 */
function BooleanRectangularToggleButton( trueNode, falseNode, booleanProperty, options ) {

  options = merge( { tandem: Tandem.REQUIRED }, options );

  assert && assert( !options.content, 'options.content cannot be set' );
  options.content = new BooleanToggleNode( trueNode, falseNode, booleanProperty );

  RectangularToggleButton.call( this, false, true, booleanProperty, options );

  // @private {function} - internally used disposal function
  this.disposeBooleanRectangularToggleButton = function() {
    options.content.dispose();
  };
}

sun.register( 'BooleanRectangularToggleButton', BooleanRectangularToggleButton );

inherit( RectangularToggleButton, BooleanRectangularToggleButton, {
  /**
   * @public
   */
  dispose: function() {
    this.disposeBooleanRectangularToggleButton();
    RectangularToggleButton.prototype.dispose.call( this );
  }
} );

export default BooleanRectangularToggleButton;<commit_msg>convert BooleanRectangularToggleButton and subclasses to ES6, https://github.com/phetsims/tasks/issues/1044<commit_after>// Copyright 2013-2020, University of Colorado Boulder

/**
 * This toggle button uses a boolean property and a trueNode and falseNode to display its content.
 */

import merge from '../../../phet-core/js/merge.js';
import Tandem from '../../../tandem/js/Tandem.js';
import BooleanToggleNode from '../BooleanToggleNode.js';
import sun from '../sun.js';
import RectangularToggleButton from './RectangularToggleButton.js';

class BooleanRectangularToggleButton extends RectangularToggleButton {

  /**
   * @param {Node} trueNode
   * @param {Node} falseNode
   * @param {Property.<boolean>} booleanProperty
   * @param {Object} [options]
   */
  constructor( trueNode, falseNode, booleanProperty, options ) {

    options = merge( {
      tandem: Tandem.REQUIRED
    }, options );

    assert && assert( !options.content, 'options.content cannot be set' );
    options.content = new BooleanToggleNode( trueNode, falseNode, booleanProperty );

    super( false, true, booleanProperty, options );

    // @private
    this.disposeBooleanRectangularToggleButton = () => {
      options.content && options.content.dispose();
    };
  }

  /**
   * @public
   * @override
   */
  dispose() {
    this.disposeBooleanRectangularToggleButton();
    super.dispose();
  }
}

sun.register( 'BooleanRectangularToggleButton', BooleanRectangularToggleButton );
export default BooleanRectangularToggleButton;<|endoftext|>


<commit_before>var request = require('request'),
    log = require('bole')('npme-create-trial')
    config = require('../../../config');

module.exports = function (customer, callback) {

  var trialEndpoint = config.license.api + '/trial',
      productId = config.npme.product_id;

  // check if they already have a trial; 1 per customer
  request.get({
    url: trialEndpoint + '/' + productId + '/' + customer.email,
    json: true
  }, function(er, resp, trial) {

    switch (resp.statusCode) {
      case 200:
        // they already have a trial
        return callback(null, trial);
      case 404:
        // do not already have a trial, so create one
        return createNewTrial(customer, callback);
      default:
        var msg = 'Error with getting trial info for ' + customer.email;
        log.error(msg);
        er = er || new Error(msg);
        log.error(er);

        return callback(er);
    }
  });
};

function createNewTrial (customer, callback) {

  var trialEndpoint = config.license.api + '/trial',
      productId = config.npme.product_id,
      trialLength = config.npme.trial_length,
      trialSeats = config.npme.trial_seats;

  request.put({
    url: trialEndpoint,
    json: {
      customer_id: customer.id,
      product_id: productId,
      length: trialLength,
      seats: trialSeats
    }
  }, function (err, resp, trial) {

    if (resp.statusCode === 200) {
      return callback(null, trial);
    }

    var msg = 'Error with creating a trial for ' + customer.email;
    err = err || new Error(msg);
    log.error(msg);
    log.error(err);
    return callback(err);
  });
}<commit_msg>Missssssinnnnng commmaaaaaaaa<commit_after>var request = require('request'),
    log = require('bole')('npme-create-trial'),
    config = require('../../../config');

module.exports = function (customer, callback) {

  var trialEndpoint = config.license.api + '/trial',
      productId = config.npme.product_id;

  // check if they already have a trial; 1 per customer
  request.get({
    url: trialEndpoint + '/' + productId + '/' + customer.email,
    json: true
  }, function(er, resp, trial) {

    switch (resp.statusCode) {
      case 200:
        // they already have a trial
        return callback(null, trial);
      case 404:
        // do not already have a trial, so create one
        return createNewTrial(customer, callback);
      default:
        var msg = 'Error with getting trial info for ' + customer.email;
        log.error(msg);
        er = er || new Error(msg);
        log.error(er);

        return callback(er);
    }
  });
};

function createNewTrial (customer, callback) {

  var trialEndpoint = config.license.api + '/trial',
      productId = config.npme.product_id,
      trialLength = config.npme.trial_length,
      trialSeats = config.npme.trial_seats;

  request.put({
    url: trialEndpoint,
    json: {
      customer_id: customer.id,
      product_id: productId,
      length: trialLength,
      seats: trialSeats
    }
  }, function (err, resp, trial) {

    if (resp.statusCode === 200) {
      return callback(null, trial);
    }

    var msg = 'Error with creating a trial for ' + customer.email;
    err = err || new Error(msg);
    log.error(msg);
    log.error(err);
    return callback(err);
  });
}<|endoftext|>


<commit_before>import { elementIds } from './modal';
import { preventModalBodyTouch, preventModalOverlayTouch } from './modal';

/**
 * Helper method to check if element is hidden, since we cannot use :visible without jQuery
 * @param {HTMLElement} element The element to check for visibility
 * @returns {boolean} true if element is hidden
 * @private
 */
function elementIsHidden(element) {
  return element.offsetWidth === 0 && element.offsetHeight === 0;
}

/**
 * Get the element from an option object
 *
 * @method getElementFromObject
 * @param Object attachTo
 * @returns {Element}
 * @private
 */
function getElementFromObject(attachTo) {
  const op = attachTo.element;

  if (op instanceof HTMLElement) {
    return op;
  }

  return document.querySelector(op);
}

/**
 * Return the element for a step
 *
 * @method getElementForStep
 * @param step step the step to get an element for
 * @returns {Element} the element for this step
 * @private
 */
function getElementForStep(step) {
  const { options: { attachTo } } = step;

  if (!attachTo) {
    return null;
  }

  const type = typeof attachTo;

  let element;

  if (type === 'string') {
    element = getElementFromString(attachTo);
  } else if (type === 'object') {
    element = getElementFromObject(attachTo);
  } else {
    /* istanbul ignore next: cannot test undefined attachTo, but it does work! */
    element = null;
  }
  return element;
}

/**
 * Get the element from an option string
 *
 * @method getElementFromString
 * @param element the string in the step configuration
 * @returns {Element} the element from the string
 * @private
 */
function getElementFromString(element) {
  const [selector] = element.split(' ');

  return document.querySelector(selector);
}

function addStepEventListeners() {
  if (typeof this._onScreenChange === 'function') {
    window.removeEventListener('resize', this._onScreenChange, false);
    window.removeEventListener('scroll', this._onScreenChange, false);
  }

  window.addEventListener('resize', this._onScreenChange, false);
  window.addEventListener('scroll', this._onScreenChange, false);

  const overlay = document.querySelector(`#${elementIds.modalOverlay}`);
  // Prevents window from moving on touch.
  window.addEventListener('touchmove', preventModalBodyTouch, { passive: false });

  // Allows content to move on touch.
  if (overlay) {
    overlay.addEventListener('touchmove', preventModalOverlayTouch, false);
  }
}

export {
  addStepEventListeners,
  elementIsHidden,
  getElementForStep
};
<commit_msg>Apply scroll listener to all scroll events (#322)<commit_after>import { elementIds } from './modal';
import { preventModalBodyTouch, preventModalOverlayTouch } from './modal';

/**
 * Helper method to check if element is hidden, since we cannot use :visible without jQuery
 * @param {HTMLElement} element The element to check for visibility
 * @returns {boolean} true if element is hidden
 * @private
 */
function elementIsHidden(element) {
  return element.offsetWidth === 0 && element.offsetHeight === 0;
}

/**
 * Get the element from an option object
 *
 * @method getElementFromObject
 * @param Object attachTo
 * @returns {Element}
 * @private
 */
function getElementFromObject(attachTo) {
  const op = attachTo.element;

  if (op instanceof HTMLElement) {
    return op;
  }

  return document.querySelector(op);
}

/**
 * Return the element for a step
 *
 * @method getElementForStep
 * @param step step the step to get an element for
 * @returns {Element} the element for this step
 * @private
 */
function getElementForStep(step) {
  const { options: { attachTo } } = step;

  if (!attachTo) {
    return null;
  }

  const type = typeof attachTo;

  let element;

  if (type === 'string') {
    element = getElementFromString(attachTo);
  } else if (type === 'object') {
    element = getElementFromObject(attachTo);
  } else {
    /* istanbul ignore next: cannot test undefined attachTo, but it does work! */
    element = null;
  }
  return element;
}

/**
 * Get the element from an option string
 *
 * @method getElementFromString
 * @param element the string in the step configuration
 * @returns {Element} the element from the string
 * @private
 */
function getElementFromString(element) {
  const [selector] = element.split(' ');

  return document.querySelector(selector);
}

function addStepEventListeners() {
  if (typeof this._onScreenChange === 'function') {
    window.removeEventListener('resize', this._onScreenChange, false);
    window.removeEventListener('scroll', this._onScreenChange, true);
  }

  window.addEventListener('resize', this._onScreenChange, false);
  window.addEventListener('scroll', this._onScreenChange, true);

  const overlay = document.querySelector(`#${elementIds.modalOverlay}`);
  // Prevents window from moving on touch.
  window.addEventListener('touchmove', preventModalBodyTouch, { passive: false });

  // Allows content to move on touch.
  if (overlay) {
    overlay.addEventListener('touchmove', preventModalOverlayTouch, false);
  }
}

export {
  addStepEventListeners,
  elementIsHidden,
  getElementForStep
};
<|endoftext|>


<commit_before>/*jslint node: true */
exports.config = {
    directConnect: true,

    allScriptsTimeout: 60000,

    specs: [
        'test/e2e/*.js'
    ],

    capabilities: {
        'browserName': 'chrome'
    },

    baseUrl: 'http://localhost:3000',

    framework: 'jasmine',

    jasmineNodeOpts: {
        showColors: true,
        defaultTimeoutInterval: 60000
    },

    sauceUser: process.env.SAUCE_USERNAME,
    sauceKey: process.env.SAUCE_ACCESS_KEY

};

if (process.env.TRAVIS_BUILD_NUMBER) {
    exports.config.seleniumAddress = 'http://localhost:4445/wd/hub';
    exports.config.capabilities = {
        'name': 'Juice-Shop e2e Tests (Protractor)',
        'browserName': 'firefox',
        'username': process.env.SAUCE_USERNAME,
        'accessKey': process.env.SAUCE_ACCESS_KEY,
        'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
        'build': process.env.TRAVIS_BUILD_NUMBER,
        'tags': [process.env.TRAVIS_BRANCH, process.env.TRAVIS_BUILD_NUMBER]
    }

}
<commit_msg>using chrome<commit_after>/*jslint node: true */
exports.config = {
    directConnect: true,

    allScriptsTimeout: 60000,

    specs: [
        'test/e2e/*.js'
    ],

    capabilities: {
        'browserName': 'chrome'
    },

    baseUrl: 'http://localhost:3000',

    framework: 'jasmine',

    jasmineNodeOpts: {
        showColors: true,
        defaultTimeoutInterval: 60000
    },

    sauceUser: process.env.SAUCE_USERNAME,
    sauceKey: process.env.SAUCE_ACCESS_KEY

};

if (process.env.TRAVIS_BUILD_NUMBER) {
    exports.config.seleniumAddress = 'http://localhost:4445/wd/hub';
    exports.config.capabilities = {
        'name': 'Juice-Shop e2e Tests (Protractor)',
        'browserName': 'chrome',
        'username': process.env.SAUCE_USERNAME,
        'accessKey': process.env.SAUCE_ACCESS_KEY,
        'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
        'build': process.env.TRAVIS_BUILD_NUMBER,
        'tags': [process.env.TRAVIS_BRANCH, process.env.TRAVIS_BUILD_NUMBER]
    }

}
<|endoftext|>