<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.controllers.app', []);

    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
        }
    });

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

    module.controller("AppControllerLocking", function($scope, $timeout,
                $window, 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) {
                // Space with shift, toggle play / pause.
                evt.preventDefault();
                evt.stopPropagation();
                VideoPlayer.togglePlay();
            }
            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) {
        $scope.currentEdit = new CurrentEditManager();
        $scope.workingSubtitles = new SubtitleVersionManager(SubtitleStorage);
        $scope.referenceSubtitles = new SubtitleVersionManager(
                SubtitleStorage);

        var editingVersion = EditorData.editingVersion;

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

        $scope.saveSubtitles = function() {
            return SubtitleStorage.saveSubtitles(
                    $scope.videoId,
                    $scope.workingSubtitles.language.code,
                    $scope.workingSubtitles.subtitleList.toXMLString(),
                    $scope.workingSubtitles.title,
                    $scope.workingSubtitles.description);
        };
    });

    /*
     * Handles the workflow progression area
     */

    module.controller('WorkflowProgressionController', function($scope, EditorData, VideoPlayer) {

        function rewindPlayback() {
            VideoPlayer.pause();
            VideoPlayer.seek(0);
        }

        $scope.endorse = function() {
            if(EditorData.task_id === undefined ||
                    EditorData.task_id === null) {
                $scope.$root.$emit('save', false);
            } else {
                $scope.$root.$emit('approve-task');
            }
        }

        $scope.onNextClicked = function(evt) {
            if($scope.workflow.stage == 'type') {
                $scope.workflow.switchStage('sync');
                if(!$scope.timelineShown) {
                    $scope.toggleTimelineShown();
                }
                rewindPlayback();
            } else if ($scope.workflow.stage == 'sync') {
                $scope.workflow.switchStage('review');
                rewindPlayback();
            }
            evt.preventDefault();
        }
    });

    /* CurrentEditManager manages the current in-progress edit
     */
    CurrentEditManager = function() {
        this.draft = null;
        this.LI = null;
    }

    CurrentEditManager.prototype = {
        start: function(subtitle, LI) {
            this.draft = subtitle.draftSubtitle();
            this.LI = LI;
        },
        finish: function(commitChanges, subtitleList) {
            var updateNeeded = (commitChanges && this.changed());
            if(updateNeeded) {
                subtitleList.updateSubtitleContent(this.draft.storedSubtitle,
                        this.currentMarkdown());
            }
            this.draft = this.LI = null;
            return updateNeeded;
        },
        storedSubtitle: function() {
            if(this.draft !== null) {
                return this.draft.storedSubtitle;
            } else {
                return null;
            }
        },
        sourceMarkdown: function() {
            return this.draft.storedSubtitle.markdown;
        },
        currentMarkdown: function() {
            return this.draft.markdown;
        },
        changed: function() {
            return this.sourceMarkdown() != this.currentMarkdown();
        },
         update: function(markdown) {
            if(this.draft !== null) {
                this.draft.markdown = markdown;
            }
         },
         isForSubtitle: function(subtitle) {
            return (this.draft !== null && this.draft.storedSubtitle == subtitle);
         },
         inProgress: function() {
            return this.draft !== null;
         },
         lineCounts: function() {
             if(this.draft === null || this.draft.lineCount() < 2) {
                 // Only show the line counts if there are 2 or more lines
                 return null;
             } else {
                 return this.draft.characterCountPerLine();
             }
         },
    };

    /*
     * SubtitleVersionManager: handle the active subtitle version for the
     * reference and working subs.
     *
     */

    SubtitleVersionManager = function(SubtitleStorage) {
        this.SubtitleStorage = SubtitleStorage;
        this.subtitleList = new dfxp.SubtitleList();
        this.versionNumber = null;
        this.language = null;
        this.title = null;
        this.description = null;
        this.state = 'waiting';
    }

    SubtitleVersionManager.prototype = {
        getSubtitles: function(languageCode, versionNumber) {
            this.setLanguage(languageCode);
            this.versionNumber = versionNumber;
            this.state = 'loading';

            var that = this;

            this.SubtitleStorage.getSubtitles(languageCode, versionNumber,
                    function(subtitleData) {
                that.state = 'loaded';
                that.title = subtitleData.title;
                that.description = subtitleData.description;
                that.subtitleList.loadXML(subtitleData.subtitlesXML);
            });
        },
        initEmptySubtitles: function(languageCode) {
            this.setLanguage(languageCode);
            this.versionNumber = null;
            this.title = this.description = '';
            this.subtitleList.loadXML(null);
            this.state = 'loaded';
        },
        setLanguage: function(code) {
            this.language = this.SubtitleStorage.getLanguage(code);
        },
    };

    Workflow = function(subtitleList) {
        var self = this;
        this.subtitleList = subtitleList;
        if(this.subtitleList.length() == 0) {
            this.stage = 'type';
        } else {
            this.stage = 'sync';
        }
        this.subtitleList.addChangeCallback(function() {
            if(self.stage == 'review' && !self.canMoveToReview()) {
                self.stage = 'sync';
            }
        });
    }

    Workflow.prototype = {
        switchStage: function(newStage) {
            if(newStage == 'review' && !this.canMoveToReview()) {
                return;
            }
            this.stage = newStage;
        },
        canMoveToReview: function() {
            return (this.subtitleList.length() > 0 &&
                    !this.subtitleList.needsAnyTranscribed() &&
                    !this.subtitleList.needsAnySynced());
        },
        stageDone: function(stageName) {
            if(stageName == 'type') {
                return (this.stage == 'review' || this.stage == 'sync');
            } else if(stageName == 'sync') {
                return this.stage == 'review'
            } else {
                return false;
            }
        },
    }

    /* Export modal classes as values.  This makes testing and dependency
     * injection easier.
     */

    module.value('CurrentEditManager', CurrentEditManager);
    module.value('SubtitleVersionManager', SubtitleVersionManager);
    module.value('Workflow', Workflow);
}).call(this);
<commit_msg>Removed unused dependency.<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.controllers.app', []);

    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
        }
    });

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

    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) {
                // Space with shift, toggle play / pause.
                evt.preventDefault();
                evt.stopPropagation();
                VideoPlayer.togglePlay();
            }
            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) {
        $scope.currentEdit = new CurrentEditManager();
        $scope.workingSubtitles = new SubtitleVersionManager(SubtitleStorage);
        $scope.referenceSubtitles = new SubtitleVersionManager(
                SubtitleStorage);

        var editingVersion = EditorData.editingVersion;

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

        $scope.saveSubtitles = function() {
            return SubtitleStorage.saveSubtitles(
                    $scope.videoId,
                    $scope.workingSubtitles.language.code,
                    $scope.workingSubtitles.subtitleList.toXMLString(),
                    $scope.workingSubtitles.title,
                    $scope.workingSubtitles.description);
        };
    });

    /*
     * Handles the workflow progression area
     */

    module.controller('WorkflowProgressionController', function($scope, EditorData, VideoPlayer) {

        function rewindPlayback() {
            VideoPlayer.pause();
            VideoPlayer.seek(0);
        }

        $scope.endorse = function() {
            if(EditorData.task_id === undefined ||
                    EditorData.task_id === null) {
                $scope.$root.$emit('save', false);
            } else {
                $scope.$root.$emit('approve-task');
            }
        }

        $scope.onNextClicked = function(evt) {
            if($scope.workflow.stage == 'type') {
                $scope.workflow.switchStage('sync');
                if(!$scope.timelineShown) {
                    $scope.toggleTimelineShown();
                }
                rewindPlayback();
            } else if ($scope.workflow.stage == 'sync') {
                $scope.workflow.switchStage('review');
                rewindPlayback();
            }
            evt.preventDefault();
        }
    });

    /* CurrentEditManager manages the current in-progress edit
     */
    CurrentEditManager = function() {
        this.draft = null;
        this.LI = null;
    }

    CurrentEditManager.prototype = {
        start: function(subtitle, LI) {
            this.draft = subtitle.draftSubtitle();
            this.LI = LI;
        },
        finish: function(commitChanges, subtitleList) {
            var updateNeeded = (commitChanges && this.changed());
            if(updateNeeded) {
                subtitleList.updateSubtitleContent(this.draft.storedSubtitle,
                        this.currentMarkdown());
            }
            this.draft = this.LI = null;
            return updateNeeded;
        },
        storedSubtitle: function() {
            if(this.draft !== null) {
                return this.draft.storedSubtitle;
            } else {
                return null;
            }
        },
        sourceMarkdown: function() {
            return this.draft.storedSubtitle.markdown;
        },
        currentMarkdown: function() {
            return this.draft.markdown;
        },
        changed: function() {
            return this.sourceMarkdown() != this.currentMarkdown();
        },
         update: function(markdown) {
            if(this.draft !== null) {
                this.draft.markdown = markdown;
            }
         },
         isForSubtitle: function(subtitle) {
            return (this.draft !== null && this.draft.storedSubtitle == subtitle);
         },
         inProgress: function() {
            return this.draft !== null;
         },
         lineCounts: function() {
             if(this.draft === null || this.draft.lineCount() < 2) {
                 // Only show the line counts if there are 2 or more lines
                 return null;
             } else {
                 return this.draft.characterCountPerLine();
             }
         },
    };

    /*
     * SubtitleVersionManager: handle the active subtitle version for the
     * reference and working subs.
     *
     */

    SubtitleVersionManager = function(SubtitleStorage) {
        this.SubtitleStorage = SubtitleStorage;
        this.subtitleList = new dfxp.SubtitleList();
        this.versionNumber = null;
        this.language = null;
        this.title = null;
        this.description = null;
        this.state = 'waiting';
    }

    SubtitleVersionManager.prototype = {
        getSubtitles: function(languageCode, versionNumber) {
            this.setLanguage(languageCode);
            this.versionNumber = versionNumber;
            this.state = 'loading';

            var that = this;

            this.SubtitleStorage.getSubtitles(languageCode, versionNumber,
                    function(subtitleData) {
                that.state = 'loaded';
                that.title = subtitleData.title;
                that.description = subtitleData.description;
                that.subtitleList.loadXML(subtitleData.subtitlesXML);
            });
        },
        initEmptySubtitles: function(languageCode) {
            this.setLanguage(languageCode);
            this.versionNumber = null;
            this.title = this.description = '';
            this.subtitleList.loadXML(null);
            this.state = 'loaded';
        },
        setLanguage: function(code) {
            this.language = this.SubtitleStorage.getLanguage(code);
        },
    };

    Workflow = function(subtitleList) {
        var self = this;
        this.subtitleList = subtitleList;
        if(this.subtitleList.length() == 0) {
            this.stage = 'type';
        } else {
            this.stage = 'sync';
        }
        this.subtitleList.addChangeCallback(function() {
            if(self.stage == 'review' && !self.canMoveToReview()) {
                self.stage = 'sync';
            }
        });
    }

    Workflow.prototype = {
        switchStage: function(newStage) {
            if(newStage == 'review' && !this.canMoveToReview()) {
                return;
            }
            this.stage = newStage;
        },
        canMoveToReview: function() {
            return (this.subtitleList.length() > 0 &&
                    !this.subtitleList.needsAnyTranscribed() &&
                    !this.subtitleList.needsAnySynced());
        },
        stageDone: function(stageName) {
            if(stageName == 'type') {
                return (this.stage == 'review' || this.stage == 'sync');
            } else if(stageName == 'sync') {
                return this.stage == 'review'
            } else {
                return false;
            }
        },
    }

    /* Export modal classes as values.  This makes testing and dependency
     * injection easier.
     */

    module.value('CurrentEditManager', CurrentEditManager);
    module.value('SubtitleVersionManager', SubtitleVersionManager);
    module.value('Workflow', Workflow);
}).call(this);
<|endoftext|>


<commit_before>'use strict';

var App = Jesm.createClass({

	__construct: function(root){
		var cvs = document.createElement('canvas');

		this.html = {
			canvas: cvs
		};

		Jesm.addEvento(cvs, 'click', this._click, this);
		// Jesm.addEvento(window, 'resize', this._resize, this);
		this._resize();
		root.appendChild(cvs);

		AppElement.startOn(cvs);

		// this.startGame();
	},

	_resize: function(){
		var size = Jesm.Cross.inner(), cvs = this.html.canvas;
		cvs.width = size[0];
		cvs.height = size[1];
	},

	_click: function(ev){
		new Dot(this, ev.clientX, ev.clientY);
	}

});

App.getAsColorString = function(arr){
	arr = arr.slice();
	for(var len = arr.length; len--;)
		arr[len] = len == 3 ? arr[len].toFixed(4) : Math.round(arr[len]);
	return 'rgb' + (arr.length == 4 ? 'a' : '') + '(' + arr.join(',') + ')';
};

var AppElement = Jesm.createClass({

	__construct: function(app){
		this.app = app;
		this._modifiers = {};
		this.zIndex = 1;
		this._deleteInNextFrame = false;

		AppElement.addInstance(this);
	},

	startModifier: function(str, toValue, duration){
		var arr = str.split('.'),
			currentValue = this._modifier(arr),
			modifiers = [];

		if(Array.isArray(currentValue)){
			var size = Math.min(currentValue.length, toValue.length);
			for(var len = size; len--;){
				var tmpArr = arr.slice();
				tmpArr.push(len);
				modifiers.push({
					propArr: tmpArr,
					easer: new Jesm.Easer(currentValue[len], toValue[len], duration).start()
				});
			}
		}
		else{
			modifiers.push({
				propArr: arr,
				easer: new Jesm.Easer(currentValue, toValue, duration).start()
			});
		}

		this._modifiers[str] = modifiers;
	},

	_calculateModifiers: function(timestamp){
		for(var key in this._modifiers){
			var arr = this._modifiers[key];

			for(var len = arr.length; len--;){
				var mod = arr[len],
					value = mod.easer.gerar(timestamp.now);

				this._modifier(mod.propArr, value);
				if(mod.easer.isComplete())
					arr.splice(len, 1);
			}
		}
	},

	_modifier: function(arr, value){
		var obj = this, index = 0;
		for(var finalIndex = arr.length - 1; index < finalIndex; index++)
			obj = obj[arr[index]];
		if(value != null)
			obj[arr[index]] = value;
		return obj[arr[index]];
	},

	removeFromCanvas: function(){
		this._deleteInNextFrame = true;
	}

});

AppElement._instances = [];

AppElement.addInstance = function(obj){
	for(var x = 0, len = this._instances.length; x < len; x++){
		var element = this._instances[x];
		if(element.zIndex < obj.zIndex){
			this._instances.splice(x, 0, obj);
			return;
		}
	}

	this._instances.push(obj);
};

AppElement.startOn = function(canvas){
	this._canvas = canvas;
	this._ctxt = canvas.getContext('2d');

	this.timestamp = {
		now: + new Date()
	};

	this.render();
}

AppElement._sortElements = function(a, b){
	return b.zIndex - a.zIndex;
}

AppElement.render = function(){
	requestAnimationFrame(this.render.bind(this)); // Requests new frame

	var now = + new Date();
	this.timestamp.elapsedTime = now - this.timestamp.now;
	this.timestamp.now = now;

	this._ctxt.clearRect(0, 0, this._canvas.width, this._canvas.height);
	this._instances.sort(this._sortElements);

	for(var len = this._instances.length; len--;){
		var element = this._instances[len];

		if(element._deleteInNextFrame){
			this._instances.splice(len, 1);
			continue;
		}

		element._calculateModifiers(this.timestamp);
		element.draw(this._ctxt);
	}
}

var Dot = AppElement.extend({

	__construct: function(app, x, y){
		this._super(app);

		this.x = x;
		this.y = y;
		this.diameter = 50;
		this.background = [200, 100, 0, 1];

		// this.startModifier('x', this.x * 2, 3);
		this.startModifier('diameter', this.diameter * 2, 3);
		this.startModifier('background', [255, 255, 255, 0], 3);
	},

	draw: function(ctxt){
		var path = new Path2D();
		path.arc(this.x, this.y, this.diameter, 0, Math.PI * 2, true);
		ctxt.fillStyle = App.getAsColorString(this.background);
		ctxt.fill(path);
	}

});<commit_msg>Adicionar opcao de callback nos modificadores<commit_after>'use strict';

var App = Jesm.createClass({

	__construct: function(root){
		var cvs = document.createElement('canvas');

		this.html = {
			canvas: cvs
		};

		Jesm.addEvento(cvs, 'click', this._click, this);
		// Jesm.addEvento(window, 'resize', this._resize, this);
		this._resize();
		root.appendChild(cvs);

		AppElement.startOn(cvs);

		// this.startGame();
	},

	_resize: function(){
		var size = Jesm.Cross.inner(), cvs = this.html.canvas;
		cvs.width = size[0];
		cvs.height = size[1];
	},

	_click: function(ev){
		new Dot(this, ev.clientX, ev.clientY);
	}

});

App.getAsColorString = function(arr){
	arr = arr.slice();
	for(var len = arr.length; len--;)
		arr[len] = len == 3 ? arr[len].toFixed(4) : Math.round(arr[len]);
	return 'rgb' + (arr.length == 4 ? 'a' : '') + '(' + arr.join(',') + ')';
};

var AppElement = Jesm.createClass({

	__construct: function(app){
		this.app = app;
		this._modifiers = {};
		this.zIndex = 1;
		this._deleteInNextFrame = false;

		AppElement.addInstance(this);
	},

	startModifier: function(str, toValue, duration, callback){
		var arr = str.split('.'),
			currentValue = this._modifier(arr),
			obj = {
				list: [],
				callback: callback
			};

		if(Array.isArray(currentValue)){
			var size = Math.min(currentValue.length, toValue.length);
			for(var len = size; len--;){
				var tmpArr = arr.slice();
				tmpArr.push(len);
				obj.list.push({
					propArr: tmpArr,
					easer: new Jesm.Easer(currentValue[len], toValue[len], duration).start()
				});
			}
		}
		else{
			obj.list.push({
				propArr: arr,
				easer: new Jesm.Easer(currentValue, toValue, duration).start()
			});
		}

		this._modifiers[str] = obj;
	},

	_calculateModifiers: function(timestamp){
		for(var key in this._modifiers){
			var obj = this._modifiers[key],
				arr = obj.list,
				toEnd = arr.length;

			for(var len = arr.length; len--;){
				var mod = arr[len],
					value = mod.easer.gerar(timestamp.now);

				this._modifier(mod.propArr, value);
				if(mod.easer.isComplete()){
					arr.splice(len, 1);
					toEnd--;
				}
			}

			if(!toEnd){
				delete this._modifiers[key];
				if(Jesm.isFunction(obj.callback))
					obj.callback.call(this);
			}
		}
	},

	_modifier: function(arr, value){
		var obj = this, index = 0;
		for(var finalIndex = arr.length - 1; index < finalIndex; index++)
			obj = obj[arr[index]];
		if(value != null)
			obj[arr[index]] = value;
		return obj[arr[index]];
	},

	removeFromCanvas: function(){
		this._deleteInNextFrame = true;
	}

});

AppElement._instances = [];

AppElement.addInstance = function(obj){
	for(var x = 0, len = this._instances.length; x < len; x++){
		var element = this._instances[x];
		if(element.zIndex < obj.zIndex){
			this._instances.splice(x, 0, obj);
			return;
		}
	}

	this._instances.push(obj);
};

AppElement.startOn = function(canvas){
	this._canvas = canvas;
	this._ctxt = canvas.getContext('2d');

	this.timestamp = {
		now: + new Date()
	};

	this.render();
}

AppElement._sortElements = function(a, b){
	return b.zIndex - a.zIndex;
}

AppElement.render = function(){
	requestAnimationFrame(this.render.bind(this)); // Requests new frame

	var now = + new Date();
	this.timestamp.elapsedTime = now - this.timestamp.now;
	this.timestamp.now = now;

	this._ctxt.clearRect(0, 0, this._canvas.width, this._canvas.height);
	this._instances.sort(this._sortElements);

	for(var len = this._instances.length; len--;){
		var element = this._instances[len];

		if(element._deleteInNextFrame){
			this._instances.splice(len, 1);
			continue;
		}

		element._calculateModifiers(this.timestamp);
		element.draw(this._ctxt);
	}
}

var Dot = AppElement.extend({

	__construct: function(app, x, y){
		this._super(app);

		this.x = x;
		this.y = y;
		this.diameter = 50;
		this.background = [200, 100, 0, 1];

		// this.startModifier('x', this.x * 2, 3);
		this.startModifier('diameter', this.diameter * 2, 3);
		this.startModifier('background', [255, 255, 255, 0], 3, this.removeFromCanvas);
	},

	draw: function(ctxt){
		var path = new Path2D();
		path.arc(this.x, this.y, this.diameter, 0, Math.PI * 2, true);
		ctxt.fillStyle = App.getAsColorString(this.background);
		ctxt.fill(path);
	}

});<|endoftext|>


<commit_before>'use strict'
require('script!./three.js');
require('script!./OBJLoader.js');
require('script!./OBJMTLLoader.js');
require('script!./MTLLoader.js');
require('script!./threex.spaceships.js');

function models(scene) {

	//loadSpaceFrigate(scene);
	loadSpaceFighther01(scene);
}
module.exports = models;

function loadSpaceFrigate(scene){

				var manager = new THREE.LoadingManager();
				manager.onProgress = function ( item, loaded, total ) {
					console.log( item, loaded, total );
				};
				var texture = new THREE.Texture();
				var onProgress = function ( xhr ) {
					if ( xhr.lengthComputable ) {
						var percentComplete = xhr.loaded / xhr.total * 100;
						console.log( Math.round(percentComplete, 2) + '% downloaded' );
					}
				};
				var onError = function ( xhr ) {
				};
	var loader = new THREE.ImageLoader( manager );
				loader.load( 'models/space_frigate/space_frigate_6_color.png', function ( image ) {
					texture.image = image;
					texture.needsUpdate = true;
				} );

	var loader = new THREE.OBJLoader( manager );
				loader.load( 'models/space_frigate/space_frigate.obj', function ( object ) {
					object.traverse( function ( child ) {
						if ( child instanceof THREE.Mesh ) {
							child.material.map = texture;
						}
					} );
					object.position.y = 20;
					object.position.z = 0;
					scene.add( object );
				}, onProgress, onError);
}

function loadSpaceFighther01(scene){
	THREEx.SpaceShips.loadSpaceFighter01(function(object3d){
    scene.add(object3d);
	});
}

function loadSpaceFighther02(scene){
THREEx.SpaceShips.loadSpaceFighter02(function(object3d){
    scene.add(object3d);
	});
}

function loadSpaceFighther03(scene){
THREEx.SpaceShips.loadSpaceFighter03(function(object3d){
    scene.add(object3d);
	});
}

function loadShuttle01(scene){
THREEx.SpaceShips.loadShuttle01(function(object3d){
    scene.add(object3d);
	});
}

function loadShuttle02(scene){
THREEx.SpaceShips.loadShuttle02(function(object3d){
    scene.add(object3d);
	});
}
<commit_msg>added custom objMtlLoader<commit_after>'use strict'
require('script!./three.js');
require('script!./OBJLoader.js');
require('script!./OBJMTLLoader.js');
require('script!./MTLLoader.js');
//require('script!./threex.spaceships.js');

function models(scene) {

	// instantiate a loader
	var loader = new THREE.OBJMTLLoader();

	loadObjMtl(scene, loader, 'models/SpaceFighter02/SpaceFighter02');

}
module.exports = models;

function loadObjMtl(scene, loader, resource){
	// load an obj / mtl resource pair
	loader.load(
	// OBJ resource URL
	resource + '.obj',
	// MTL resource UR
	resource + '.mtl',
	// Function when both resources are loaded
	function ( object ) {
		object.traverse(function(object3d){
			if( object.material ){
				object.material.emissive.set('white')
			}
		})
		scene.add( object );
	},
	// Function called when downloads progress
	function ( xhr ) { console.log( (xhr.loaded / xhr.total * 100) + '% loaded' ); },
	// Function called when downloads error
	function ( xhr ) { console.log( 'An error happened' ); } );

}

function loadSpaceFighther02(scene){
THREEx.SpaceShips.loadSpaceFighter02(function(object3d){
    scene.add(object3d);
	});
}

function loadSpaceFighther03(scene){
THREEx.SpaceShips.loadSpaceFighter03(function(object3d){
    scene.add(object3d);
	});
}

function loadShuttle01(scene){
THREEx.SpaceShips.loadShuttle01(function(object3d){
    scene.add(object3d);
	});
}

function loadShuttle02(scene){
THREEx.SpaceShips.loadShuttle02(function(object3d){
    scene.add(object3d);
	});
}
<|endoftext|>


<commit_before>////////////////////////////////////////////////
//          Navbar  Event  Listeners          //
////////////////////////////////////////////////

/* Remove Map */

// 'Remove Map' on click removes both text and map
$(document.body).ready(function () {
  $("#remove-map").on('click', function () {
    $('#map').css('opacity', '0');
    $('.jumbotron').css('display', 'none');
  });
});

/* Sponsors */

// add sponsor logos to .container #sponsors
// function insertSponsors() {
//   var sponsorImages = document.getElementById("sponsors").children;

//   // if sponsor image exists, remove it on click instead
//   if (sponsorImages.length > 1) {
//     sponsorImages.nodeValue = "";
//   }
//   else {
//     sponsorImages[0].nodeValue = "  ";
//     sponsorImages[1].nodeValue = "  ";
//     sponsorImages[2].nodeValue = "  ";
//     sponsorImages[3].nodeValue = "  ";
//     sponsorImages[3].nodeValue = "  ";
//   }
// }

var createSponsors = function(src, sponsors) {
  var sponsors = document.getElementById(sponsors);
  var image = document.createElement('img');
  image.setAttribute('src', src);
  sponsors.appendChild(image);
};

// add event listener
var showSponsors = document.getElementById("show-sponsors");
showSponsors.addEventListener("click", function(){
  if (sponsors.children.length > 1) {
    sponsors.innerHTML = "";
  }
  else {
  createSponsors('images/snowbird-logo.png', 'sponsors');
  createSponsors('images/snocountry_logo.png', 'sponsors');
  createSponsors('images/google-maps-logo.jpg', 'sponsors');
  createSponsors('images/killington-logo.png', 'sponsors');
  createSponsors('images/mammoth-logo.jpg', 'sponsors');
}
}, false);<commit_msg>#3 Remove unused code, and clean spacing.<commit_after>////////////////////////////////////////////////
//          Navbar  Event  Listeners          //
////////////////////////////////////////////////

/* Remove Map */

// 'Remove Map' on click removes both text and map
$(document.body).ready(function () {
  $("#remove-map").on('click', function () {
    $('#map').css('opacity', '0');
    $('.jumbotron').css('display', 'none');
  });
});

/* Sponsors */

var createSponsors = function(src, sponsors) {
  var sponsors = document.getElementById(sponsors);
  var image = document.createElement('img');
  image.setAttribute('src', src);
  sponsors.appendChild(image);
};

// add event listener
var showSponsors = document.getElementById("show-sponsors");
showSponsors.addEventListener("click", function(){
  if (sponsors.children.length > 1) {
    sponsors.innerHTML = "";
  }
  else {
  createSponsors('images/snowbird-logo.png', 'sponsors');
  createSponsors('images/snocountry_logo.png', 'sponsors');
  createSponsors('images/google-maps-logo.jpg', 'sponsors');
  createSponsors('images/killington-logo.png', 'sponsors');
  createSponsors('images/mammoth-logo.jpg', 'sponsors');
  }
}, false);<|endoftext|>


<commit_before>// Forward to an SMTP server as a proxy.
// Opens the connection to the ongoing SMTP server at MAIL FROM time
// and passes back any errors seen on the ongoing server to the originating server.

var os   = require('os');
var sock = require('./line_socket');

var smtp_regexp = /^([0-9]{3})([ -])(.*)/;

exports.hook_mail = function (next, connection, params) {
    this.loginfo("smtp proxying");
    var mail_from = params[0];
    var smtp_proxy = {};
    smtp_proxy.config = this.config.get('smtp_proxy.ini', 'ini');
    smtp_proxy.socket = new sock.Socket();
    smtp_proxy.socket.connect(smtp_proxy.config.main.port, smtp_proxy.config.main.host);
    smtp_proxy.socket.setTimeout(300 * 1000); // 5m timeout
    var self = this;
    smtp_proxy.command = 'connect';
    smtp_proxy.response = [];
    var data_marker = 0;
    smtp_proxy.recipient_marker = 0;
    connection.transaction.notes.smtp_proxy = smtp_proxy;
    smtp_proxy.next = next;

    smtp_proxy.send_data = function () {
        if (data_marker < connection.transaction.data_lines.length) {
            var wrote_all = smtp_proxy.socket.write(connection.transaction.data_lines[data_marker].replace(/^\./, '..').replace(/\r?\n/g, '\r\n'));
            data_marker++;
            if (wrote_all) {
                smtp_proxy.send_data();
            }
        }
        else {
            smtp_proxy.socket.send_command('dot');
        }
    }

    smtp_proxy.socket.on('error', function (err) {
        self.logerror("Ongoing connection failed: " + err);
        smtp_proxy.socket.destroy();
        delete connection.transaction.notes.smtp_proxy;
        // we don't deny on error - maybe another plugin can deliver
        smtp_proxy.next();
    });

    smtp_proxy.socket.on('timeout', function () {
        self.logerror("Ongoing connection timed out");
        smtp_proxy.socket.destroy();
        delete connection.transaction.notes.smtp_proxy;
        smtp_proxy.next();
    });

    smtp_proxy.socket.on('connect', function () {});

    smtp_proxy.socket.send_command = function (cmd, data) {
        var line = cmd + (data ? (' ' + data) : '');
        if (cmd === 'dot') {
            line = '.';
        }
        self.logprotocol("Proxy C: " + line);
        this.write(line + "\r\n");
        smtp_proxy.command = cmd.toLowerCase();
    };

    smtp_proxy.socket.on('line', function (line) {
        var matches;
        self.logprotocol("Proxy S: " + line);
        if (matches = smtp_regexp.exec(line)) {
            var code = matches[1],
                cont = matches[2],
                rest = matches[3];
            smtp_proxy.response.push(rest);
            if (cont === ' ') {
                if (code.match(/^[45]/)) {
                    if (smtp_proxy.command !== 'rcpt') {
                        // errors are OK for rcpt, but nothing else
                        smtp_proxy.socket.send_command('QUIT');
                        smtp_proxy.command = 'quit';
                    }
                    return smtp_proxy.next(code.match(/^4/) ? DENYSOFT : DENY, smtp_proxy.response);
                }
                smtp_proxy.response = []; // reset the response now we're done with it
                switch (smtp_proxy.command) {
                    case 'connect':
                        smtp_proxy.socket.send_command('HELO', self.config.get('me'));
                        break;
                    case 'helo':
                        smtp_proxy.socket.send_command('MAIL', 'FROM:' + mail_from);
                        break;
                    case 'mail':
                        return smtp_proxy.next();
                    case 'rcpt':
                        return smtp_proxy.next();
                    case 'data':
                        return smtp_proxy.next();
                    case 'dot':
                        smtp_proxy.socket.send_command('QUIT');
                        return smtp_proxy.next(OK);
                    case 'quit':
                        smtp_proxy.socket.destroySoon();
                        break;
                    default:
                        throw "Unknown command: " + smtp_proxy.command;
                }
            }
        }
        else {
            // Unrecognised response.
            self.logerror("Unrecognised response from upstream server: " + line);
            smtp_proxy.socket.destroy();
            return smtp_proxy.next(DENYSOFT);
        }
    });
    smtp_proxy.socket.on('drain', function() {
        self.logprotocol("Drained");
        if (smtp_proxy.command === 'dot') {
            smtp_proxy.send_data();
        }
    });
};

exports.hook_rcpt_ok = function (next, connection, recipient) {
    if (!connection.transaction.notes.smtp_proxy) return next();
    var smtp_proxy = connection.transaction.notes.smtp_proxy;
    smtp_proxy.next = next;
    smtp_proxy.socket.send_command('RCPT', 'TO:' + recipient);
};

exports.hook_data = function (next, connection) {
    if (!connection.transaction.notes.smtp_proxy) return next();
    var smtp_proxy = connection.transaction.notes.smtp_proxy;
    smtp_proxy.next = next;
    smtp_proxy.socket.send_command("DATA");
};

exports.hook_queue = function (next, connection) {
    if (!connection.transaction.notes.smtp_proxy) return next();
    var smtp_proxy = connection.transaction.notes.smtp_proxy;
    smtp_proxy.command = 'dot';
    smtp_proxy.next = next;
    smtp_proxy.send_data();
};
<commit_msg>Check for presence of transaction in case we've already reset it.<commit_after>// Forward to an SMTP server as a proxy.
// Opens the connection to the ongoing SMTP server at MAIL FROM time
// and passes back any errors seen on the ongoing server to the originating server.

var os   = require('os');
var sock = require('./line_socket');

var smtp_regexp = /^([0-9]{3})([ -])(.*)/;

exports.hook_mail = function (next, connection, params) {
    this.loginfo("smtp proxying");
    var mail_from = params[0];
    var smtp_proxy = {};
    smtp_proxy.config = this.config.get('smtp_proxy.ini', 'ini');
    smtp_proxy.socket = new sock.Socket();
    smtp_proxy.socket.connect(smtp_proxy.config.main.port, smtp_proxy.config.main.host);
    smtp_proxy.socket.setTimeout(300 * 1000); // 5m timeout
    var self = this;
    smtp_proxy.command = 'connect';
    smtp_proxy.response = [];
    var data_marker = 0;
    smtp_proxy.recipient_marker = 0;
    connection.transaction.notes.smtp_proxy = smtp_proxy;
    smtp_proxy.next = next;

    smtp_proxy.send_data = function () {
        if (data_marker < connection.transaction.data_lines.length) {
            var wrote_all = smtp_proxy.socket.write(connection.transaction.data_lines[data_marker].replace(/^\./, '..').replace(/\r?\n/g, '\r\n'));
            data_marker++;
            if (wrote_all) {
                smtp_proxy.send_data();
            }
        }
        else {
            smtp_proxy.socket.send_command('dot');
        }
    }

    smtp_proxy.socket.on('error', function (err) {
        self.logerror("Ongoing connection failed: " + err);
        smtp_proxy.socket.destroy();
        if (connection.transaction)
            delete connection.transaction.notes.smtp_proxy;
        // we don't deny on error - maybe another plugin can deliver
        smtp_proxy.next();
    });

    smtp_proxy.socket.on('timeout', function () {
        self.logerror("Ongoing connection timed out");
        smtp_proxy.socket.destroy();
        if (connection.transaction)
            delete connection.transaction.notes.smtp_proxy;
        smtp_proxy.next();
    });

    smtp_proxy.socket.on('connect', function () {});

    smtp_proxy.socket.send_command = function (cmd, data) {
        var line = cmd + (data ? (' ' + data) : '');
        if (cmd === 'dot') {
            line = '.';
        }
        self.logprotocol("Proxy C: " + line);
        this.write(line + "\r\n");
        smtp_proxy.command = cmd.toLowerCase();
    };

    smtp_proxy.socket.on('line', function (line) {
        var matches;
        self.logprotocol("Proxy S: " + line);
        if (matches = smtp_regexp.exec(line)) {
            var code = matches[1],
                cont = matches[2],
                rest = matches[3];
            smtp_proxy.response.push(rest);
            if (cont === ' ') {
                if (code.match(/^[45]/)) {
                    if (smtp_proxy.command !== 'rcpt') {
                        // errors are OK for rcpt, but nothing else
                        smtp_proxy.socket.send_command('QUIT');
                        smtp_proxy.command = 'quit';
                    }
                    return smtp_proxy.next(code.match(/^4/) ? DENYSOFT : DENY, smtp_proxy.response);
                }
                smtp_proxy.response = []; // reset the response now we're done with it
                switch (smtp_proxy.command) {
                    case 'connect':
                        smtp_proxy.socket.send_command('HELO', self.config.get('me'));
                        break;
                    case 'helo':
                        smtp_proxy.socket.send_command('MAIL', 'FROM:' + mail_from);
                        break;
                    case 'mail':
                        return smtp_proxy.next();
                    case 'rcpt':
                        return smtp_proxy.next();
                    case 'data':
                        return smtp_proxy.next();
                    case 'dot':
                        smtp_proxy.socket.send_command('QUIT');
                        return smtp_proxy.next(OK);
                    case 'quit':
                        smtp_proxy.socket.destroySoon();
                        break;
                    default:
                        throw "Unknown command: " + smtp_proxy.command;
                }
            }
        }
        else {
            // Unrecognised response.
            self.logerror("Unrecognised response from upstream server: " + line);
            smtp_proxy.socket.destroy();
            return smtp_proxy.next(DENYSOFT);
        }
    });
    smtp_proxy.socket.on('drain', function() {
        self.logprotocol("Drained");
        if (smtp_proxy.command === 'dot') {
            smtp_proxy.send_data();
        }
    });
};

exports.hook_rcpt_ok = function (next, connection, recipient) {
    if (!connection.transaction.notes.smtp_proxy) return next();
    var smtp_proxy = connection.transaction.notes.smtp_proxy;
    smtp_proxy.next = next;
    smtp_proxy.socket.send_command('RCPT', 'TO:' + recipient);
};

exports.hook_data = function (next, connection) {
    if (!connection.transaction.notes.smtp_proxy) return next();
    var smtp_proxy = connection.transaction.notes.smtp_proxy;
    smtp_proxy.next = next;
    smtp_proxy.socket.send_command("DATA");
};

exports.hook_queue = function (next, connection) {
    if (!connection.transaction.notes.smtp_proxy) return next();
    var smtp_proxy = connection.transaction.notes.smtp_proxy;
    smtp_proxy.command = 'dot';
    smtp_proxy.next = next;
    smtp_proxy.send_data();
};
<|endoftext|>


<commit_before>#!/usr/bin/env node

var express = require('express');
var gss = require('gss');
var http = require('http');
var path = require('path');
var Q = require('q');
var util = require('util');

var auth = require('../lib/auth.js');
var conf = require('../lib/config.js');
var connections = require('../lib/connections.js');
var db = require('../lib/db.js');
var error = require('../lib/error.js');
var msgid = require('../lib/msgid.js');
var Subscriber = require('../lib/subscriber.js').Subscriber;

// Set the keytab.
var realAuth = false;
if (conf.get('serverKeytab')) {
  process.env['KRB5_KTNAME'] = conf.get('serverKeytab');
  realAuth = true;
} else {
  console.error('!!!!!!!!!!!!!!!!!!!!!');
  console.error('No keytab set. Using fake authentication');
  console.error('Do NOT run this in production!');
  console.error('!!!!!!!!!!!!!!!!!!!!!');
  if (conf.get('production'))
    process.exit(1);
}

var subscriber = new Subscriber();

var app = express();

app.use(function(req, res, next) {
  // Pretend a text/plain Content-Type is actually
  // application/json. This is a silly hack to avoid a CORS preflight
  // request. Apart from three whitelisted Content-Type values, other
  // settings require a CORS preflight to avoid introducing CSRF
  // vulnerabilities in existing AJAX applications. We don't assume a
  // JSON content-type is same-origin, so it's fine for us.
  //
  // In addition, this hack is required for IE9 because XDomainRequest
  // predates CORS and can only send text/plain anyway.
  if (/^text\/plain($|;)/.test(req.headers['content-type']))
    req.headers['content-type'] = 'application/json;charset=utf-8';
  next();
});
app.use(express.bodyParser());

// Don't cache anything. This is an API.
app.use(function(req, res, next) {
  // TODO(davidben): This doesn't get at the socket.io entry points,
  // which still contains an access_token query parameter.
  res.set('Cache-Control', 'no-cache,no-store,private');
  next();
});

// CORS ALL THE THINGS. We won't use cookies and this is different
// from Access-Control-Allow-Credentials. So we're fine.
app.use(function(req, res, next) {
  res.set('Access-Control-Allow-Origin', '*');
  // TODO(davidben): Unfortunately, using JSON as the Content-Type
  // doesn't save us from preflights.
  res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});
// Just accept the preflight everywhere. Whatever.
app.options('*', function(req, res) {
  // Pick up the headers from earlier.
  res.send(200);
});

function extractBearerToken(req) {
  // IE9 CORS (really XDomainRequest) doesn't allow custom headers.
  // And even with CORS, an Authorization header would require a
  // preflight. Allow passing the token in the query string. Google
  // does this for their APIs.
  //
  // See http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html

  // Authorization Request Header Field
  var authorization = req.get('Authorization');
  if (authorization) {
    var m = /^Bearer +(.*)$/.exec(authorization);
    if (m)
      return m[1];
  }

  // TODO(davidben): Implement the "Form-Encoded Body Parameter" while
  // we're at it? Doesn't seem much point, since we don't use
  // application/x-www-form-urlencoded. Doing the equivalent for JSON
  // is not insane though.

  // URI Query Parameter
  if (req.query.access_token)
    return req.query.access_token;

  return null;
}

function requireUser(req, res, next) {
  var token = extractBearerToken(req);
  if (!token) {
    // Appease the HTTP gods who say you need a WWW-Authenticate
    // header when you send back 401. Hopefully this'll prevent a
    // browser prompt. They're bad about not prompting...
    res.set('WWW-Authenticate', 'Bearer');
    res.send(401, 'Auth token required');
    return;
  }
  auth.checkAuthToken(token).then(function(user) {
    req.user = user;
    next();
  }, function(err) {
    sendError(res, err);
    console.error(err);
  });
}

function jsonAPI(fn) {
  return function(req, res) {
    Q.fcall(fn, req).then(function(ret) {
      res.json(ret);
    }, function(err) {
      if (err instanceof error.UserError) {
        // Blegh.
        if (err.code == 401)
          res.set('WWW-Authenticate', 'Bearer');
        res.send(err.code, err.msg);
      } else {
        res.send(500);
        console.error(err);
      }
    }).done();
  };
}

app.post('/v1/auth', jsonAPI(function(req) {
  var principal, respTokenB64;
  if (realAuth) {
    if (typeof req.body.token !== 'string') {
      throw new error.UserError(400, 'Token expected');
    }

    var context = gss.createAcceptor(null);
    try {
      var respToken =
        context.acceptSecContext(new Buffer(req.body.token, 'base64'));
    } catch (e) {
      // TODO(davidben): Get the KRB_ERROR out and send it back or
      // something.
      console.error(e.toString(), e);
      throw new error.UserError(403, 'Bad token');
    }

    if (!context.isEstablished()) {
      // We don't support multi-legged auth. But this shouldn't
      // happen.
      throw 'ERROR: GSS context did not establish in iteration!';
    }

    principal = context.srcName().toString();
    if (respToken != null)
      respTokenB64 = respToken.toString('base64');
  } else {
    principal = req.body.principal;
    if (typeof principal !== "string") {
      throw new error.UserError(400, 'Principal expected');
    }
  }

  var userPromise;
  if (req.body.createUser) {
    // Create user and make default subs.
    userPromise = db.getOrCreateUser(principal).then(function(user) {
      if (!user.newUser)
        return user;
      return subscriber.addDefaultUserSubscriptions(user).then(function() {
        return user;
      });
    });
  } else {
    userPromise = db.getUser(principal);
  }

  return userPromise.then(function(user) {
    if (user == null)
      throw new error.UserError(403, 'User does not exist');
    return user;
  }).then(function(user) {
    var ret = auth.makeAuthToken(user);
    return {
      gssToken: respTokenB64,
      authToken: ret.token,
      expires: ret.expires
    };
  });
}));

app.get('/v1/ping', requireUser, jsonAPI(function(req) {
  return { pong: 1 };
}));

app.get('/v1/info', requireUser, jsonAPI(function(req) {
  return db.getUserInfo(req.user);
}));

app.post('/v1/info', requireUser, jsonAPI(function(req) {
  if (typeof req.body.info != 'string' ||
      typeof req.body.expectedVersion != 'number') {
    throw new error.UserError(400, 'info and expectedVersion required');
  }
  return db.updateUserInfo(
    req.user, req.body.info, req.body.expectedVersion
  ).then(function(updated) {
    if (updated)
      return { updated: true };
    return db.getUserInfo(req.user).then(function(ret) {
      return {
        updated: false,
        version: ret.version,
        info: ret.info
      };
    });
  });
}));

app.get('/v1/subscriptions', requireUser, jsonAPI(function(req) {
  return db.getUserSubscriptions(req.user);
}));

function isValidSub(sub) {
  if (typeof sub !== 'object')
    return false;
  if (typeof sub.class !== 'string')
    return false;
  if (typeof sub.instance !== 'string')
    return false;
  if (typeof sub.recipient !== 'string')
    return false;
  return true;
}

app.post('/v1/subscribe', requireUser, jsonAPI(function(req) {
  if (!util.isArray(req.body.subscriptions) ||
      !req.body.subscriptions.every(isValidSub)) {
    // TODO(davidben): Nicer error message.
    throw new error.UserError(400, 'Subscription triples expected');
  }
  return subscriber.addUserSubscriptions(
    req.user, req.body.subscriptions, req.body.credentials);
}));

app.post('/v1/unsubscribe', requireUser, jsonAPI(function(req) {
  if (!isValidSub(req.body.subscription)) {
    // TODO(davidben): Nicer error message.
    throw new error.UserError(400, 'Subscription triple expected');
  }
  return subscriber.removeUserSubscription(
    req.user,
    req.body.subscription
  ).then(function() {
    return { unsubscribed: true };
  });
}));

app.get('/v1/messages', requireUser, jsonAPI(function(req) {
  var offset = req.query.offset;
  if (offset) {
    offset = msgid.unseal(offset);
  } else {
    // Punt the empty string too.
    offset = null;
  }
  return db.getMessages(
    req.user, offset, {
      inclusive: Boolean(req.query.inclusive|0),
      reverse: Boolean(req.query.reverse|0),
      limit: req.query.count|0
    }
  ).then(function(result) {
    result.messages.forEach(function(msg) {
      msg.id = msgid.seal(msg.id);
    });
    return result;
  })
}));

app.get('/v1/zephyrcreds', requireUser, jsonAPI(function(req) {
  return {
    needsRefresh: subscriber.needsZephyrCreds(req.user)
  };
}));

app.post('/v1/zephyrcreds', requireUser, jsonAPI(function(req) {
  if (!req.body.credentials) {
    throw new error.UserError(400, "Missing credentials parameter");
  }
  return subscriber.refreshPrivateSubs(
    req.user, req.body.credentials
  ).then(function() {
    return { refreshed: true };
  });
}));

// Serve random static files (just the sockjs client right now, needed
// for the iframe-based transports).
app.use(express.static(path.join(__dirname, '../static')));

var server = http.createServer(app);
var connectionManager = connections.listen(server, subscriber);

// Load active subscriptions from the database.
console.log('Starting subscriber...');
subscriber.start().then(function() {
  // And now we're ready to start doing things.
  console.log('...started');
  server.listen(conf.get('port'), conf.get('ip'), function() {
    var addy = server.address();
    console.log('running on http://' + addy.address + ":" + addy.port);
  });
}).done();

// Cancel subscriptions on exit.
['SIGINT', 'SIGQUIT', 'SIGTERM'].forEach(function(sig) {
  process.on(sig, function() {
    console.log('Canceling subscriptions...');
    subscriber.shutdown().then(function() {
      console.log('Bye');
      process.exit();
    }).done();
  });
});
<commit_msg>Set Vary header<commit_after>#!/usr/bin/env node

var express = require('express');
var gss = require('gss');
var http = require('http');
var path = require('path');
var Q = require('q');
var util = require('util');

var auth = require('../lib/auth.js');
var conf = require('../lib/config.js');
var connections = require('../lib/connections.js');
var db = require('../lib/db.js');
var error = require('../lib/error.js');
var msgid = require('../lib/msgid.js');
var Subscriber = require('../lib/subscriber.js').Subscriber;

// Set the keytab.
var realAuth = false;
if (conf.get('serverKeytab')) {
  process.env['KRB5_KTNAME'] = conf.get('serverKeytab');
  realAuth = true;
} else {
  console.error('!!!!!!!!!!!!!!!!!!!!!');
  console.error('No keytab set. Using fake authentication');
  console.error('Do NOT run this in production!');
  console.error('!!!!!!!!!!!!!!!!!!!!!');
  if (conf.get('production'))
    process.exit(1);
}

var subscriber = new Subscriber();

var app = express();

app.use(function(req, res, next) {
  // Pretend a text/plain Content-Type is actually
  // application/json. This is a silly hack to avoid a CORS preflight
  // request. Apart from three whitelisted Content-Type values, other
  // settings require a CORS preflight to avoid introducing CSRF
  // vulnerabilities in existing AJAX applications. We don't assume a
  // JSON content-type is same-origin, so it's fine for us.
  //
  // In addition, this hack is required for IE9 because XDomainRequest
  // predates CORS and can only send text/plain anyway.
  if (/^text\/plain($|;)/.test(req.headers['content-type']))
    req.headers['content-type'] = 'application/json;charset=utf-8';
  next();
});
app.use(express.bodyParser());

// Don't cache anything. This is an API.
app.use(function(req, res, next) {
  // TODO(davidben): This doesn't get at the socket.io entry points,
  // which still contains an access_token query parameter.
  res.set('Cache-Control', 'no-cache,no-store,private');
  next();
});

// CORS ALL THE THINGS. We won't use cookies and this is different
// from Access-Control-Allow-Credentials. So we're fine.
app.use(function(req, res, next) {
  res.set('Access-Control-Allow-Origin', '*');
  // TODO(davidben): Unfortunately, using JSON as the Content-Type
  // doesn't save us from preflights.
  res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});
// Just accept the preflight everywhere. Whatever.
app.options('*', function(req, res) {
  // Pick up the headers from earlier.
  res.send(200);
});

function extractBearerToken(req) {
  // IE9 CORS (really XDomainRequest) doesn't allow custom headers.
  // And even with CORS, an Authorization header would require a
  // preflight. Allow passing the token in the query string. Google
  // does this for their APIs.
  //
  // See http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html

  // Authorization Request Header Field
  var authorization = req.get('Authorization');
  if (authorization) {
    var m = /^Bearer +(.*)$/.exec(authorization);
    if (m)
      return m[1];
  }

  // TODO(davidben): Implement the "Form-Encoded Body Parameter" while
  // we're at it? Doesn't seem much point, since we don't use
  // application/x-www-form-urlencoded. Doing the equivalent for JSON
  // is not insane though.

  // URI Query Parameter
  if (req.query.access_token)
    return req.query.access_token;

  return null;
}

function requireUser(req, res, next) {
  res.set('Vary', 'Authorization');
  var token = extractBearerToken(req);
  if (!token) {
    // Appease the HTTP gods who say you need a WWW-Authenticate
    // header when you send back 401. Hopefully this'll prevent a
    // browser prompt. They're bad about not prompting...
    res.set('WWW-Authenticate', 'Bearer');
    res.send(401, 'Auth token required');
    return;
  }
  auth.checkAuthToken(token).then(function(user) {
    req.user = user;
    next();
  }, function(err) {
    sendError(res, err);
    console.error(err);
  });
}

function jsonAPI(fn) {
  return function(req, res) {
    Q.fcall(fn, req).then(function(ret) {
      res.json(ret);
    }, function(err) {
      if (err instanceof error.UserError) {
        // Blegh.
        if (err.code == 401)
          res.set('WWW-Authenticate', 'Bearer');
        res.send(err.code, err.msg);
      } else {
        res.send(500);
        console.error(err);
      }
    }).done();
  };
}

app.post('/v1/auth', jsonAPI(function(req) {
  var principal, respTokenB64;
  if (realAuth) {
    if (typeof req.body.token !== 'string') {
      throw new error.UserError(400, 'Token expected');
    }

    var context = gss.createAcceptor(null);
    try {
      var respToken =
        context.acceptSecContext(new Buffer(req.body.token, 'base64'));
    } catch (e) {
      // TODO(davidben): Get the KRB_ERROR out and send it back or
      // something.
      console.error(e.toString(), e);
      throw new error.UserError(403, 'Bad token');
    }

    if (!context.isEstablished()) {
      // We don't support multi-legged auth. But this shouldn't
      // happen.
      throw 'ERROR: GSS context did not establish in iteration!';
    }

    principal = context.srcName().toString();
    if (respToken != null)
      respTokenB64 = respToken.toString('base64');
  } else {
    principal = req.body.principal;
    if (typeof principal !== "string") {
      throw new error.UserError(400, 'Principal expected');
    }
  }

  var userPromise;
  if (req.body.createUser) {
    // Create user and make default subs.
    userPromise = db.getOrCreateUser(principal).then(function(user) {
      if (!user.newUser)
        return user;
      return subscriber.addDefaultUserSubscriptions(user).then(function() {
        return user;
      });
    });
  } else {
    userPromise = db.getUser(principal);
  }

  return userPromise.then(function(user) {
    if (user == null)
      throw new error.UserError(403, 'User does not exist');
    return user;
  }).then(function(user) {
    var ret = auth.makeAuthToken(user);
    return {
      gssToken: respTokenB64,
      authToken: ret.token,
      expires: ret.expires
    };
  });
}));

app.get('/v1/ping', requireUser, jsonAPI(function(req) {
  return { pong: 1 };
}));

app.get('/v1/info', requireUser, jsonAPI(function(req) {
  return db.getUserInfo(req.user);
}));

app.post('/v1/info', requireUser, jsonAPI(function(req) {
  if (typeof req.body.info != 'string' ||
      typeof req.body.expectedVersion != 'number') {
    throw new error.UserError(400, 'info and expectedVersion required');
  }
  return db.updateUserInfo(
    req.user, req.body.info, req.body.expectedVersion
  ).then(function(updated) {
    if (updated)
      return { updated: true };
    return db.getUserInfo(req.user).then(function(ret) {
      return {
        updated: false,
        version: ret.version,
        info: ret.info
      };
    });
  });
}));

app.get('/v1/subscriptions', requireUser, jsonAPI(function(req) {
  return db.getUserSubscriptions(req.user);
}));

function isValidSub(sub) {
  if (typeof sub !== 'object')
    return false;
  if (typeof sub.class !== 'string')
    return false;
  if (typeof sub.instance !== 'string')
    return false;
  if (typeof sub.recipient !== 'string')
    return false;
  return true;
}

app.post('/v1/subscribe', requireUser, jsonAPI(function(req) {
  if (!util.isArray(req.body.subscriptions) ||
      !req.body.subscriptions.every(isValidSub)) {
    // TODO(davidben): Nicer error message.
    throw new error.UserError(400, 'Subscription triples expected');
  }
  return subscriber.addUserSubscriptions(
    req.user, req.body.subscriptions, req.body.credentials);
}));

app.post('/v1/unsubscribe', requireUser, jsonAPI(function(req) {
  if (!isValidSub(req.body.subscription)) {
    // TODO(davidben): Nicer error message.
    throw new error.UserError(400, 'Subscription triple expected');
  }
  return subscriber.removeUserSubscription(
    req.user,
    req.body.subscription
  ).then(function() {
    return { unsubscribed: true };
  });
}));

app.get('/v1/messages', requireUser, jsonAPI(function(req) {
  var offset = req.query.offset;
  if (offset) {
    offset = msgid.unseal(offset);
  } else {
    // Punt the empty string too.
    offset = null;
  }
  return db.getMessages(
    req.user, offset, {
      inclusive: Boolean(req.query.inclusive|0),
      reverse: Boolean(req.query.reverse|0),
      limit: req.query.count|0
    }
  ).then(function(result) {
    result.messages.forEach(function(msg) {
      msg.id = msgid.seal(msg.id);
    });
    return result;
  })
}));

app.get('/v1/zephyrcreds', requireUser, jsonAPI(function(req) {
  return {
    needsRefresh: subscriber.needsZephyrCreds(req.user)
  };
}));

app.post('/v1/zephyrcreds', requireUser, jsonAPI(function(req) {
  if (!req.body.credentials) {
    throw new error.UserError(400, "Missing credentials parameter");
  }
  return subscriber.refreshPrivateSubs(
    req.user, req.body.credentials
  ).then(function() {
    return { refreshed: true };
  });
}));

// Serve random static files (just the sockjs client right now, needed
// for the iframe-based transports).
app.use(express.static(path.join(__dirname, '../static')));

var server = http.createServer(app);
var connectionManager = connections.listen(server, subscriber);

// Load active subscriptions from the database.
console.log('Starting subscriber...');
subscriber.start().then(function() {
  // And now we're ready to start doing things.
  console.log('...started');
  server.listen(conf.get('port'), conf.get('ip'), function() {
    var addy = server.address();
    console.log('running on http://' + addy.address + ":" + addy.port);
  });
}).done();

// Cancel subscriptions on exit.
['SIGINT', 'SIGQUIT', 'SIGTERM'].forEach(function(sig) {
  process.on(sig, function() {
    console.log('Canceling subscriptions...');
    subscriber.shutdown().then(function() {
      console.log('Bye');
      process.exit();
    }).done();
  });
});
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * navbar.js
 *
 * Code handling the navigation bar. The navigation bar includes
 * the back/forward/stop/reload buttons, the url bar and the search
 * bar.
 *
 */

require(['js/urlhelper', 'js/tabiframedeck', 'js/keybindings'],
function(UrlHelper, TabIframeDeck, RegisterKeyBindings) {

  'use strict';

  let link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = 'css/navbar.css';
  let defaultStyleSheet = document.querySelector('link[title=default]');
  document.head.insertBefore(link, defaultStyleSheet.nextSibling);

  let html = `
    <hbox class='navbar toolbar' align='center'>
      <button class='back-button'></button>
      <button class='forward-button'></button>
      <button class='reload-button'></button>
      <button class='stop-button'></button>
      <hbox class='urlbar' flex='1' align='center'>

        <input placeholder='Search or enter address' class='urlinput' flex='1'>
      </hbox>
      <hbox class='searchbar' flex='1' align='center'>

        <input placeholder='Yahoo' class='searchinput'>
      </hbox>
      <button class='menu-button'></button>
    </hbox>
  `;
  let outervbox = document.querySelector('#outervbox');
  let outerhbox = document.querySelector('#outerhbox');
  let placeholder = document.createElement('hbox');
  outervbox.insertBefore(placeholder, outerhbox);
  placeholder.outerHTML = html;

  let navbar = document.querySelector('.navbar');

  let urlTemplate = 'https://search.yahoo.com/search?p={searchTerms}';

  let urlbar = navbar.querySelector('.urlbar');
  let urlinput = navbar.querySelector('.urlinput');
  let searchbar = navbar.querySelector('.searchbar');
  let searchinput = navbar.querySelector('.searchinput');
  let backButton = navbar.querySelector('.back-button')
  let forwardButton = navbar.querySelector('.forward-button')
  let reloadButton = navbar.querySelector('.reload-button');
  let stopButton = navbar.querySelector('.stop-button');

  backButton.onclick = () => TabIframeDeck.getSelected().goBack();
  forwardButton.onclick = () => TabIframeDeck.getSelected().goForward();
  reloadButton.onclick = () => TabIframeDeck.getSelected().reload();
  stopButton.onclick = () => TabIframeDeck.getSelected().stop();

  urlinput.addEventListener('focus', () => {
    urlinput.select();
    urlbar.classList.add('focus');
  })

  urlinput.addEventListener('blur', () => {
    urlbar.classList.remove('focus');
  })

  urlinput.addEventListener('keypress', (e) => {
    if (e.keyCode == 13) {
      UrlInputChanged()
    }
  });

  urlinput.addEventListener('input', () => {
    TabIframeDeck.getSelected().userInput = urlinput.value;
  });

  searchinput.addEventListener('focus', () => {
    searchinput.select();
    searchbar.classList.add('focus');
  })
  searchinput.addEventListener('blur', () => searchbar.classList.remove('focus'))
  searchinput.addEventListener('keypress', (e) => {
    if (e.keyCode == 13) {
      SearchInputChanged()
    }
  });

  let mod = window.OS == 'osx' ? 'Cmd' : 'Ctrl';

  RegisterKeyBindings(
    [mod,    'l',   () => {
      urlinput.focus();
      urlinput.select();
    }],
    [mod,    'k',   () => {
      searchinput.focus();
      searchinput.select();
    }]
  );

  function UrlInputChanged() {
    let text = urlinput.value;
    let url = PreprocessUrlInput(text);
    let tabIframe = TabIframeDeck.getSelected();
    tabIframe.setLocation(url);
    tabIframe.focus();
  }

  function SearchInputChanged() {
    let text = searchinput.value;
    let url = urlTemplate.replace('{searchTerms}', encodeURIComponent(text));
    let tabIframe = TabIframeDeck.getSelected();
    tabIframe.setLocation(url);
    tabIframe.focus();
  }

  TabIframeDeck.on('select', OnTabSelected);

  let lastSelectedTab = null;

  let events = [
    'mozbrowserloadstart',
    'mozbrowserloadend',
    'mozbrowserlocationchange',
    'mozbrowsererror',
    'mozbrowsersecuritychange',
  ];

  function OnTabSelected() {
    let selectedTabIframe = TabIframeDeck.getSelected();
    if (lastSelectedTab) {
      for (let e of events) {
        lastSelectedTab.off(e, UpdateTab);
      }
    }
    lastSelectedTab = selectedTabIframe;
    if (selectedTabIframe) {
      if (!selectedTabIframe.location) {
        urlinput.focus();
        urlinput.select();
      }
      for (let e of events) {
        lastSelectedTab.on(e, UpdateTab);
      }
      UpdateTab(null, null, selectedTabIframe);
    }
  }

  OnTabSelected();

  function UpdateTab(eventName, event, tabIframe) {
    if (tabIframe != TabIframeDeck.getSelected()) {
      return;
    }

    if (tabIframe.loading) {
      navbar.classList.add('loading');
    } else {
      navbar.classList.remove('loading');
    }

    if (tabIframe.userInput) {
      urlinput.value = tabIframe.userInput;
    } else if (tabIframe.location) {
      urlinput.value = tabIframe.location;
    } else if (eventName === null) {
      urlinput.value = "";
    }

    if (!window.IS_PRIVILEGED) {
      return;
    }

    if (tabIframe.securityState == 'secure') {
      navbar.classList.add('ssl');
      if (tabIframe.securityExtendedValidation) {
        navbar.classList.add('sslev');
      }
    } else {
      navbar.classList.remove('ssl');
      navbar.classList.remove('sslev');
    }

    tabIframe.canGoBack().then(canGoBack => {
      // Make sure iframe is still selected
      if (tabIframe != TabIframeDeck.getSelected()) {
        return;
      }
      if (canGoBack) {
        backButton.classList.remove('disabled');
      } else {
        backButton.classList.add('disabled');
      }
    });

    tabIframe.canGoForward().then(canGoForward => {
      // Make sure iframe is still selected
      if (tabIframe != TabIframeDeck.getSelected()) {
        return;
      }
      if (canGoForward) {
        forwardButton.classList.remove('disabled');
      } else {
        forwardButton.classList.add('disabled');
      }
    });
  };

  function PreprocessUrlInput(input) {
    if (UrlHelper.isNotURL(input)) {
      return urlTemplate.replace('{searchTerms}', encodeURIComponent(input));
    }

    if (!UrlHelper.hasScheme(input)) {
      input = 'http://' + input;
    }

    return input;
  };

});
<commit_msg>fix quotes<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * navbar.js
 *
 * Code handling the navigation bar. The navigation bar includes
 * the back/forward/stop/reload buttons, the url bar and the search
 * bar.
 *
 */

require(['js/urlhelper', 'js/tabiframedeck', 'js/keybindings'],
function(UrlHelper, TabIframeDeck, RegisterKeyBindings) {

  'use strict';

  let link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = 'css/navbar.css';
  let defaultStyleSheet = document.querySelector('link[title=default]');
  document.head.insertBefore(link, defaultStyleSheet.nextSibling);

  let html = `
    <hbox class='navbar toolbar' align='center'>
      <button class='back-button'></button>
      <button class='forward-button'></button>
      <button class='reload-button'></button>
      <button class='stop-button'></button>
      <hbox class='urlbar' flex='1' align='center'>

        <input placeholder='Search or enter address' class='urlinput' flex='1'>
      </hbox>
      <hbox class='searchbar' flex='1' align='center'>

        <input placeholder='Yahoo' class='searchinput'>
      </hbox>
      <button class='menu-button'></button>
    </hbox>
  `;
  let outervbox = document.querySelector('#outervbox');
  let outerhbox = document.querySelector('#outerhbox');
  let placeholder = document.createElement('hbox');
  outervbox.insertBefore(placeholder, outerhbox);
  placeholder.outerHTML = html;

  let navbar = document.querySelector('.navbar');

  let urlTemplate = 'https://search.yahoo.com/search?p={searchTerms}';

  let urlbar = navbar.querySelector('.urlbar');
  let urlinput = navbar.querySelector('.urlinput');
  let searchbar = navbar.querySelector('.searchbar');
  let searchinput = navbar.querySelector('.searchinput');
  let backButton = navbar.querySelector('.back-button')
  let forwardButton = navbar.querySelector('.forward-button')
  let reloadButton = navbar.querySelector('.reload-button');
  let stopButton = navbar.querySelector('.stop-button');

  backButton.onclick = () => TabIframeDeck.getSelected().goBack();
  forwardButton.onclick = () => TabIframeDeck.getSelected().goForward();
  reloadButton.onclick = () => TabIframeDeck.getSelected().reload();
  stopButton.onclick = () => TabIframeDeck.getSelected().stop();

  urlinput.addEventListener('focus', () => {
    urlinput.select();
    urlbar.classList.add('focus');
  })

  urlinput.addEventListener('blur', () => {
    urlbar.classList.remove('focus');
  })

  urlinput.addEventListener('keypress', (e) => {
    if (e.keyCode == 13) {
      UrlInputChanged()
    }
  });

  urlinput.addEventListener('input', () => {
    TabIframeDeck.getSelected().userInput = urlinput.value;
  });

  searchinput.addEventListener('focus', () => {
    searchinput.select();
    searchbar.classList.add('focus');
  })
  searchinput.addEventListener('blur', () => searchbar.classList.remove('focus'))
  searchinput.addEventListener('keypress', (e) => {
    if (e.keyCode == 13) {
      SearchInputChanged()
    }
  });

  let mod = window.OS == 'osx' ? 'Cmd' : 'Ctrl';

  RegisterKeyBindings(
    [mod,    'l',   () => {
      urlinput.focus();
      urlinput.select();
    }],
    [mod,    'k',   () => {
      searchinput.focus();
      searchinput.select();
    }]
  );

  function UrlInputChanged() {
    let text = urlinput.value;
    let url = PreprocessUrlInput(text);
    let tabIframe = TabIframeDeck.getSelected();
    tabIframe.setLocation(url);
    tabIframe.focus();
  }

  function SearchInputChanged() {
    let text = searchinput.value;
    let url = urlTemplate.replace('{searchTerms}', encodeURIComponent(text));
    let tabIframe = TabIframeDeck.getSelected();
    tabIframe.setLocation(url);
    tabIframe.focus();
  }

  TabIframeDeck.on('select', OnTabSelected);

  let lastSelectedTab = null;

  let events = [
    'mozbrowserloadstart',
    'mozbrowserloadend',
    'mozbrowserlocationchange',
    'mozbrowsererror',
    'mozbrowsersecuritychange',
  ];

  function OnTabSelected() {
    let selectedTabIframe = TabIframeDeck.getSelected();
    if (lastSelectedTab) {
      for (let e of events) {
        lastSelectedTab.off(e, UpdateTab);
      }
    }
    lastSelectedTab = selectedTabIframe;
    if (selectedTabIframe) {
      if (!selectedTabIframe.location) {
        urlinput.focus();
        urlinput.select();
      }
      for (let e of events) {
        lastSelectedTab.on(e, UpdateTab);
      }
      UpdateTab(null, null, selectedTabIframe);
    }
  }

  OnTabSelected();

  function UpdateTab(eventName, event, tabIframe) {
    if (tabIframe != TabIframeDeck.getSelected()) {
      return;
    }

    if (tabIframe.loading) {
      navbar.classList.add('loading');
    } else {
      navbar.classList.remove('loading');
    }

    if (tabIframe.userInput) {
      urlinput.value = tabIframe.userInput;
    } else if (tabIframe.location) {
      urlinput.value = tabIframe.location;
    } else if (eventName === null) {
      urlinput.value = '';
    }

    if (!window.IS_PRIVILEGED) {
      return;
    }

    if (tabIframe.securityState == 'secure') {
      navbar.classList.add('ssl');
      if (tabIframe.securityExtendedValidation) {
        navbar.classList.add('sslev');
      }
    } else {
      navbar.classList.remove('ssl');
      navbar.classList.remove('sslev');
    }

    tabIframe.canGoBack().then(canGoBack => {
      // Make sure iframe is still selected
      if (tabIframe != TabIframeDeck.getSelected()) {
        return;
      }
      if (canGoBack) {
        backButton.classList.remove('disabled');
      } else {
        backButton.classList.add('disabled');
      }
    });

    tabIframe.canGoForward().then(canGoForward => {
      // Make sure iframe is still selected
      if (tabIframe != TabIframeDeck.getSelected()) {
        return;
      }
      if (canGoForward) {
        forwardButton.classList.remove('disabled');
      } else {
        forwardButton.classList.add('disabled');
      }
    });
  };

  function PreprocessUrlInput(input) {
    if (UrlHelper.isNotURL(input)) {
      return urlTemplate.replace('{searchTerms}', encodeURIComponent(input));
    }

    if (!UrlHelper.hasScheme(input)) {
      input = 'http://' + input;
    }

    return input;
  };

});
<|endoftext|>


<commit_before>(function($) {
  "use strict"

  $.fn.charCounter = function(options) {
    var $element = $(this)

    options = $.extend({
      limit: $element.attr('data-char-limit') || 140,
      messageContainer: '#messageContainer',
      message: "%{count} chars left."
    }, options || {})

    $element.keyup(function() {
      checkTextLength($element, options)
      updateMessageContainer($element, options)
    })

    checkTextLength($element, options)
    updateMessageContainer($element, options)
  }

  var checkTextLength = function($element, options) {
    if($element.text().length > options.limit) {
      $element.text($element.text().substring(0, options.limit))
    }
  }

  var updateMessageContainer = function($element, options) {
    var $messageContainer = $(options.messageContainer)
      , leftCharacters    = options.limit - $element.text().length
      , text              = options.message.replace('%{count}', ((leftCharacters < 0) ? 0 : leftCharacters))

    $messageContainer.text(text)
  }
})(jQuery)
<commit_msg>removed strict mode<commit_after>(function($) {
  $.fn.charCounter = function(options) {
    var $element = $(this)

    options = $.extend({
      limit: $element.attr('data-char-limit') || 140,
      messageContainer: '#messageContainer',
      message: "%{count} chars left."
    }, options || {})

    $element.keyup(function() {
      checkTextLength($element, options)
      updateMessageContainer($element, options)
    })

    checkTextLength($element, options)
    updateMessageContainer($element, options)
  }

  var checkTextLength = function($element, options) {
    if($element.text().length > options.limit) {
      $element.text($element.text().substring(0, options.limit))
    }
  }

  var updateMessageContainer = function($element, options) {
    var $messageContainer = $(options.messageContainer)
      , leftCharacters    = options.limit - $element.text().length
      , text              = options.message.replace('%{count}', ((leftCharacters < 0) ? 0 : leftCharacters))

    $messageContainer.text(text)
  }
})(jQuery)
<|endoftext|>