<commit_before>if (typeof voyent === 'undefined' || typeof voyent.io === 'undefined' || typeof voyent.xio === 'undefined') {
    throw new Error('voyent.notify.js requires voyent.js, voyent.io.js and voyent.xio.js, please include these before voyent.notify.js');
}

(function (v) {
    "use strict";

    var VOYENT_INJECT_KEY = 'voyentNotificationToInject';
    var VOYENT_QUEUE_KEY = 'voyentNotificationQueue';
    var VOYENT_TOAST_CONTAINER_ID = 'voyent_toast_container';
    var VOYENT_MAIL_QUERY_PARAMETER = 'notificationId';

    var notify;
    if (!v.notify) {
        //**************************************************************************************************************
        //************************************************* PUBLIC API *************************************************
        //**************************************************************************************************************

        notify = v.notify = {
            /**
             * @property {Object} selected - The selected notification (readonly).
             * @default null
             */
            _selected: null,
            get selected() { return this._selected; },

            /**
             * @property {Object[]} queue - The notification queue (readonly).
             * @default []
             */
            _queue: [],
            get queue() { return this._queue; },

            /**
             * @property {Number} queuePosition - The zero-based index of the selected notification in the queue (readonly).
             * @default -1
             */
            _queuePosition: -1,
            get queuePosition() { return this._queuePosition; },

            config: { //config options
                /**
                 * @property {string} autoSelectNotification - Provides options for auto selecting notifications so that 
                 * each time a selected notification is removed a new one is selected. One of [disabled|oldest|newest].
                 * @default 'disabled'
                 */
                _autoSelectNotification: 'disabled',
                set autoSelectNotification(val) {
                    val = val.toLowerCase();
                    if (['disabled','oldest','newest'].indexOf(val) > -1) {
                        this._autoSelectNotification = val;
                    }
                    else { this._autoSelectNotification = 'disabled'; }
                },
                get autoSelectNotification() { return this._autoSelectNotification; },


                /**
                 * @property {boolean} hideAfterClick - Indicates if notifications should be hidden after clicking on them.
                 * @default true
                 */
                _hideAfterClick: true,
                set hideAfterClick(val) { this._hideAfterClick = !!val; },
                get hideAfterClick() { return this._hideAfterClick; },
                
                toast: { //toast notification config options
                    /**
                     * @property {boolean} enabled - Indicates if toast notifications should be shown.
                     * @default true
                     */
                    _enabled: true,
                    set enabled(val) { this._enabled = !!val; },
                    get enabled() { return this._enabled; },

                    /**
                     * @property {Number} hideAfterMs - Time in milliseconds that the notification will be automatically
                     * hidden after shown (specify <=0 to never hide the notification, making the toast closable only by clicking).
                     * @default 5000
                     */
                    _hideAfterMs: 5000,
                    set hideAfterMs(val) {
                        if (typeof val === 'number') { this._hideAfterMs = Math.round(val); }
                        else { this._hideAfterMs = 5000; }
                    },
                    get hideAfterMs() { return this._hideAfterMs; },

                    /**
                     * @property {Number} stackLimit - Indicates the number of notifications that will be allowed to
                     * stack (specify <=0 to have no limit).
                     * @default 3
                     */
                    _stackLimit: 3,
                    set stackLimit(val) {
                        if (typeof val === 'number') { this._stackLimit = Math.round(val); }
                        else { this._stackLimit = 3; }
                    },
                    get stackLimit() { return this._stackLimit; },

                    /**
                     * @property {boolean} overwriteOld - Indicates if new toast notifications should overwrite/replace
                     * old ones in the stack.
                     * @default false
                     */
                    _overwriteOld: false,
                    set overwriteOld(val) { this._overwriteOld = !!val; },
                    get overwriteOld() { return this._overwriteOld; },

                    /**
                     * @property {string} position - Position of toast notifications on page.
                     * One of [top-right|top-left|bottom-right|bottom-left].
                     * @default 'top-right'
                     */
                    _position:'top-right',
                    set position(val) {
                        val = val.toLowerCase();
                        if (['top-right','top-left','bottom-right','bottom-left'].indexOf(val) > -1) {
                            this._position = val;
                        }
                        else { this._position = 'top-right'; }
                    },
                    get position() { return this._position; },

                    /**
                     * @property {Number} spacing - Number of pixels that the toast notifications should be spaced apart.
                     * @default 2
                     */
                    _spacing:2,
                    set spacing(val) {
                        if (typeof val === 'number') { this._spacing = Math.round(val); }
                        else { this._spacing = 2; }
                    },
                    get spacing() { return this._spacing; },

                    /**
                     * @property {string} style - Custom styling that is applied to the top-level toast notification
                     * container. Any styling defined here will override the defaults.
                     * @default ''
                     */
                    _style:'',
                    set style(val) { this._style = val.toString(); },
                    get style() { return this._style; },

                    close: { //toast notification close container config options
                        /**
                         * @property {boolean} enabled - Indicates if the close button should be shown for toast notifications.
                         * @default true
                         */
                        _enabled: true,
                        set enabled(val) { this._enabled = !!val; },
                        get enabled() { return this._enabled; },

                        /**
                         * @property {string} style - Custom styling that is applied to the toast notification close
                         * container. Any styling defined here will override the defaults.
                         * @default ''
                         */
                        _style:'',
                        set style(val) { this._style = val.toString(); },
                        get style() { return this._style; }
                    }
                },

                native: { //native/desktop notification config options
                    /**
                     * @property {boolean} enabled - Indicates if native notifications should be enabled (still must be
                     * allowed by user in browser).
                     * @default true
                     */
                    _enabled: true,
                    set enabled(val) { this._enabled = !!val; },
                    get enabled() { return this._enabled; },

                    /**
                     * @property {Number} hideAfterMs - Time in milliseconds that the notification will be automatically
                     * hidden after shown (specify <=0 to never hide the notification).
                     * @default -1
                     */
                    _hideAfterMs: -1,
                    set hideAfterMs(val) {
                        if (typeof val === 'number') { this._hideAfterMs = Math.round(val); }
                        else { this._hideAfterMs = -1; }
                    },
                    get hideAfterMs() { return this._hideAfterMs; }
                }
            },

            //Events

            //Useful for setting config options, setting up event listeners, etc...
            /**
             * Fired after the library is initialized and listening for new notifications. This is the recommended place
             * to change default configuration options if the listener is guaranteed to exist before the library loads.
             * Only fires on initial load.
             * Not cancelable.
             * @event voyentNotifyInitialized
             */
            
            /**
             * Fired after a new notification is received in the browser. Not cancelable.
             * @event notificationReceived
             */

            //Useful for previewing the new item being added to the queue and throwing it away before it's added using preventDefault().
            /**
             * Fired before the queue is updated. An update will be triggered when loading a queue from
             * storage or adding, removing or clearing the queue. Cancel the event to prevent the operation.
             * @event beforeQueueUpdated
             */

            //Useful for keeping the various aspects of the app in sync (queue, notification count, etc...)
            /**
             * Fired after the queue is updated. Not cancelable.
             * @event afterQueueUpdated
             */

            //Useful for preventing the notification from being displayed using e.preventDefault().
            /**
             * Fired before a notification is displayed. Fires for both toast and browser native notifications.
             * Cancel the event to prevent the notification from being displayed.
             * @event beforeDisplayNotification
             */

            //Useful for custom CSS effects/styling
            /**
             * Fired after a notification is displayed. Fires for both toast and browser native notifications. Not cancelable.
             * @event afterDisplayNotification
             */

            //Useful to do custom handling on the selected notification. Especially after the asynchronous call of fetching a notification from the mailbox service.
            /**
             * Fired after the selected property is changed. The notification returned may be null. If this event fires
             * in relation to a queue update then this event will always be fired AFTER the queue has been updated. Not cancelable.
             * @event notificationChanged
             */

            //Useful for custom redirecting (for apps that use custom routing).
            /**
             * Fired when a notification is clicked. Fires for both toast and browser native notifications. Cancel the
             * event to prevent the app from redirecting to the URL specified in the notification.
             * @event notificationClicked
             */

            //Useful for custom close behaviour or preventing the notification from closing.
            /**
             * Fired when a notification is closed. Fires for both toast and browser native notifications. Cancel the event
             * to prevent the notification from closing automatically (toast notifications only).
             * @event notificationClosed
             */

            //Private/internal attributes
            _listener: null, //reference to the xio.push listener

            /**
             * Start listening for notifications. This function is called automatically when the library is loaded and a
             * user is logged in via voyent.io. This function only needs to be called manually after stopListening has
             * been called.
             */
            startListening: function() {
                if (this._listener) {
                    return;
                }

                var _this = this;
                //declare push listener
                this._listener = function (notification) {
                    if (!_parseNotification(notification)) {
                        return;
                    }
                    _fireEvent('notificationReceived',{"notification":notification},false);
                    var cancelled = _fireEvent('beforeQueueUpdated',{"op":"add","notification":notification,"queue":_this.queue.slice(0)},true);
                    if (cancelled) {
                        return;
                    }
                    _this._queue.push(notification);
                    _setQueueInStorage();
                    _fireEvent('afterQueueUpdated',{"op":"add","notification":notification,"queue":_this.queue.slice(0)},false);
                    _this.displayNotification(notification);
                    if (!_this.selected) {
                        //we don't have a selected notification so set to this new one
                        _this.selectNotification(notification);
                        //inject notification data into the page if they are on
                        //the relevant page and currently have no selected notification
                        var notificationUrl = document.createElement('a'); //use anchor tag to parse notification return URL
                        notificationUrl.href = notification.url;
                        if (window.location.host === notificationUrl.host &&
                            window.location.pathname === notificationUrl.pathname) {
                            _this.injectNotificationData();
                        }
                    }
                };
                //add push listener
                v.xio.push.removeListener(this._listener);
                v.xio.push.addListener(this._listener);
            },

            /**
             * Stop listening for notifications. No new notifications will be received by the library after calling this
             * function but the other features of the library will still be available.
             */
            stopListening: function() {
                v.xio.push.removeListener(this._listener);
                this._listener = null;
                //since they explicitly stopped listening them remove the login listener as well
                window.removeEventListener('voyent-login-succeeded',_setupListeners);
            },

            /**
             * Returns an integer that represents the number of notifications currently in the queue.
             * @returns {Number} - The notification count.
             */
            getNotificationCount: function() {
                return this.queue.length;
            },

            /**
             * Returns the notification at the specified index or null if none was found.
             * @param {number} index - The zero-based index of the notification in the queue.
             * @returns {Object} - The notification.
             */
            getNotificationAt: function(index) {
                return this.queue[index] || null;
            },

            /**
             * Returns the next (newer) notification in the queue or null if there is no next.
             * @returns {Object} - The notification.
             */
            getNextNotification: function() {
                var newPos = this._queuePosition+1;
                if (this.queue[newPos]) {
                    return this.queue[newPos];
                }
                return null;
            },

            /**
             * Returns the previous (older) notification in the queue or null if there is no previous.
             * @returns {Object} - The notification.
             */
            getPreviousNotification: function() {
                var newPos = this._queuePosition-1;
                if (this.queue[newPos]) {
                    return this.queue[newPos];
                }
                return null;
            },

            /**
             * Returns the newest notification by date that is currently in the queue or null if the queue is empty.
             * @returns {Object} - The notification.
             */
            getNewestNotification: function() {
                if (this.queue.length > 0) {
                    return this.queue[this.queue.length-1];
                }
                return null;
            },

            /**
             * Returns the oldest notification by date that is currently in the queue or null if the queue is empty.
             * @returns {Object} - The notification.
             */
            getOldestNotification: function() {
                if (this.queue.length > 0) {
                    return this.queue[0];
                }
                return null;
            },

            /**
             * Removes the specified notification from the queue.
             * @param {Object} notification - The notification to be removed.
             * @returns {boolean} - Indicates if the notification was removed successfully.
             */
            removeNotification: function(notification) {
                var index = this.queue.indexOf(notification);
                if (index > -1) {
                    var cancelled = _fireEvent('beforeQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},true);
                    if (cancelled) {
                        return false;
                    }
                    //if we have an id it means the notification is stored in the
                    //mailbox service so we will delete it from the user's mail
                    if (this.queue[index].notificationId) {
                        _removeNotificationFromMailbox(this.queue[index].notificationId);
                    }
                    this._queue.splice(index,1);
                    _setQueueInStorage();
                    _fireEvent('afterQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},false);
                    return true;
                }
                return false;
            },

            /**
             * Removes the notification from the queue at the specified index.
             * @param {number} index - The zero-based index of the notification in the queue.
             * @returns {boolean} - Indicates if the notification was removed successfully.
             */
            removeNotificationAt: function(index) {
                var notification = this.queue[index];
                if (notification) {
                    var cancelled = _fireEvent('beforeQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},true);
                    if (cancelled) {
                       return false;
                    }
                    //if we have an id it means the notification is stored in the
                    //mailbox service so we will delete it from the user's mail
                    if (notification.notificationId) {
                        _removeNotificationFromMailbox(notification.notificationId);
                    }
                    this._queue.splice(index,1);
                    _setQueueInStorage();
                    _fireEvent('afterQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},false);
                    return true;
                }
                return false;
            },

            /**
             * Removes the selected notification. If successful the queuePosition will be reset to -1 indicating that no 
             * notification is currently selected.
             * @returns {boolean} - Indicates if the notification was removed successfully.
             */
            removeSelectedNotification: function() {
                if (!this.selected) {
                    return false; //nothing to remove
                }
                var notification = this.queue[this._queuePosition];
                var cancelled = _fireEvent('beforeQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},true);
                if (cancelled) {
                    return false;
                }
                //remove the notification from the queue
                this._queue.splice(this._queuePosition,1);
                _setQueueInStorage();
                this._queuePosition = -1;
                _fireEvent('afterQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},false);
                //reset the selected property
                //if we have an id it means the notification is stored in the
                //mailbox service so we will delete it from the user's mail
                if (this.selected.notificationId) {
                    _removeNotificationFromMailbox(this.selected.notificationId);
                }
                this._selected = null;
                _setSelectedNotificationInStorage();
                _fireEvent('notificationChanged',{"notification":this.selected},false);
                _autoSelectNotification();
                return true;
            },

            /**
             * Removes all notifications from the notification queue (including clearing the selected notification) and
             * resets the queuePosition to -1.
             */
            clearNotificationQueue: function() {
                if (!this.queue || this.queue.length === 0) {
                    return; //queue is already empty
                }
                var cancelled = _fireEvent('beforeQueueUpdated',{"op":"clear","queue":this.queue.slice(0)},true);
                if (cancelled) {
                    return;
                }
                for (var i=0; i<this.queue.length; i++) {
                    if (this.queue[i].notificationId) {
                        _removeNotificationFromMailbox(this.queue[i].notificationId);
                    }
                }
                this._queue = [];
                _setQueueInStorage();
                this._queuePosition = -1;
                _fireEvent('afterQueueUpdated',{"op":"clear","queue":this.queue.slice(0)},false);
                //clear the selected notification
                if (this._selected) {
                    this._selected = null;
                    _setSelectedNotificationInStorage();
                    _fireEvent('notificationChanged',{"notification":this.selected},false);
                }
            },

            /**
             * Redirects the browser to the URL specified in the passed notification and injects the notification data into the page.
             * @param {Object} notification - The notification that determines where to redirect.
             */
            redirectToNotification: function(notification) {
                if (!notification.url) {
                    return;
                }
                //save the notification to inject in session storage so it survives the redirect
                _setSelectedNotificationInStorage(notification);
                //redirect browser
                window.location.replace(notification.url);
            },

            /**
             * Injects the selected notification into elements with data-selected-* attributes.
             * Currently has special support for input and select elements. For all other elements the data
             * will be inserted as text content.
             */
            injectNotificationData: function() {
                _injectOrClearNotficationData(false);
            },

            /**
             * Removes all injected notification data from the page.
             */
            clearInjectedNotificationData: function() {
                _injectOrClearNotficationData(true);
            },

            /**
             * Sets the selected notification to the one passed if it is a valid notification in the queue.
             * @param {Object} notification - The notification object to be set.
             * @returns {boolean} - Indicates if the notification was set successfully.
             */
            selectNotification: function(notification) {
                var index = this.queue.indexOf(notification);
                if (index > -1 && this.selected !== notification) {
                    this._selected = notification;
                    this._queuePosition = index;
                    _setSelectedNotificationInStorage();
                    _fireEvent('notificationChanged',{"notification":this.selected},false);
                    return true;
                }
                return false;
            },

            /**
             * Sets the selected notification to the one in the queue at the specified index.
             * @param {number} index - The zero-based index of the notification in the queue.
             * @returns {boolean} - Indicates if the notification was set successfully.
             */
            selectNotificationAt: function(index) {
                var notification = this.queue[index];
                if (notification && notification !== this.selected) {
                    this._selected = notification;
                    this._queuePosition = index;
                    _setSelectedNotificationInStorage();
                    _fireEvent('notificationChanged',{"notification":this.selected},false);
                    return true;
                }
                return false;
            },

            /**
             * Displays the passed notification as a toast or browser native notification, depending on the current
             * configuration. Can be used to re-display a notification from the queue or even to display a custom
             * notification that is not part of the queue.
             * @param {object} notification - The notification to display.
             * @private
             */
            displayNotification: function (notification) {
                if (notify.config.native.enabled && window.Notification && Notification.permission === 'granted') {
                    _displayNativeNotification(notification);
                }
                else if (notify.config.toast.enabled) {
                    _displayToastNotification(notification);
                }
            },

            /**
             * Manually hides a notification.
             * @param {Object} notification - A toast or browser native notification reference.
             * @param {number} ms - The number of milliseconds to wait before hiding the notification.
             */
            hideNotification: function(notification,ms) {
                if (!notification || !notification.constructor) {
                    return;
                }
                setTimeout(function() {
                    if (notification.constructor === HTMLDivElement) { //toast notification<commit_msg>Documentation update<commit_after>if (typeof voyent === 'undefined' || typeof voyent.io === 'undefined' || typeof voyent.xio === 'undefined') {
    throw new Error('voyent.notify.js requires voyent.js, voyent.io.js and voyent.xio.js, please include these before voyent.notify.js');
}

(function (v) {
    "use strict";

    var VOYENT_INJECT_KEY = 'voyentNotificationToInject';
    var VOYENT_QUEUE_KEY = 'voyentNotificationQueue';
    var VOYENT_TOAST_CONTAINER_ID = 'voyent_toast_container';
    var VOYENT_MAIL_QUERY_PARAMETER = 'notificationId';

    var notify;
    if (!v.notify) {
        //**************************************************************************************************************
        //************************************************* PUBLIC API *************************************************
        //**************************************************************************************************************

        notify = v.notify = {
            /**
             * @property {Object} selected - The selected notification (readonly).
             * @default null
             */
            _selected: null,
            get selected() { return this._selected; },

            /**
             * @property {Object[]} queue - The notification queue (readonly).
             * @default []
             */
            _queue: [],
            get queue() { return this._queue; },

            /**
             * @property {Number} queuePosition - The zero-based index of the selected notification in the queue (readonly).
             * @default -1
             */
            _queuePosition: -1,
            get queuePosition() { return this._queuePosition; },

            config: { //config options
                /**
                 * @property {string} autoSelectNotification - Provides options for auto selecting notifications so that 
                 * each time a selected notification is removed a new one is selected. One of [disabled|oldest|newest].
                 * @default 'disabled'
                 */
                _autoSelectNotification: 'disabled',
                set autoSelectNotification(val) {
                    val = val.toLowerCase();
                    if (['disabled','oldest','newest'].indexOf(val) > -1) {
                        this._autoSelectNotification = val;
                    }
                    else { this._autoSelectNotification = 'disabled'; }
                },
                get autoSelectNotification() { return this._autoSelectNotification; },


                /**
                 * @property {boolean} hideAfterClick - Indicates if notifications should be hidden after clicking on them.
                 * @default true
                 */
                _hideAfterClick: true,
                set hideAfterClick(val) { this._hideAfterClick = !!val; },
                get hideAfterClick() { return this._hideAfterClick; },
                
                toast: { //toast notification config options
                    /**
                     * @property {boolean} enabled - Indicates if toast notifications should be shown.
                     * @default true
                     */
                    _enabled: true,
                    set enabled(val) { this._enabled = !!val; },
                    get enabled() { return this._enabled; },

                    /**
                     * @property {Number} hideAfterMs - Time in milliseconds that the notification will be automatically
                     * hidden after shown (specify <=0 to never hide the notification, making the toast closable only by clicking).
                     * @default 5000
                     */
                    _hideAfterMs: 5000,
                    set hideAfterMs(val) {
                        if (typeof val === 'number') { this._hideAfterMs = Math.round(val); }
                        else { this._hideAfterMs = 5000; }
                    },
                    get hideAfterMs() { return this._hideAfterMs; },

                    /**
                     * @property {Number} stackLimit - Indicates the number of notifications that will be allowed to
                     * stack (specify <=0 to have no limit).
                     * @default 3
                     */
                    _stackLimit: 3,
                    set stackLimit(val) {
                        if (typeof val === 'number') { this._stackLimit = Math.round(val); }
                        else { this._stackLimit = 3; }
                    },
                    get stackLimit() { return this._stackLimit; },

                    /**
                     * @property {boolean} overwriteOld - Indicates if new toast notifications should overwrite/replace
                     * old ones in the stack.
                     * @default false
                     */
                    _overwriteOld: false,
                    set overwriteOld(val) { this._overwriteOld = !!val; },
                    get overwriteOld() { return this._overwriteOld; },

                    /**
                     * @property {string} position - Position of toast notifications on page.
                     * One of [top-right|top-left|bottom-right|bottom-left].
                     * @default 'top-right'
                     */
                    _position:'top-right',
                    set position(val) {
                        val = val.toLowerCase();
                        if (['top-right','top-left','bottom-right','bottom-left'].indexOf(val) > -1) {
                            this._position = val;
                        }
                        else { this._position = 'top-right'; }
                    },
                    get position() { return this._position; },

                    /**
                     * @property {Number} spacing - Number of pixels that the toast notifications should be spaced apart.
                     * @default 2
                     */
                    _spacing:2,
                    set spacing(val) {
                        if (typeof val === 'number') { this._spacing = Math.round(val); }
                        else { this._spacing = 2; }
                    },
                    get spacing() { return this._spacing; },

                    /**
                     * @property {string} style - Custom styling that is applied to the top-level toast notification
                     * container. Any styling defined here will override the defaults.
                     * @default ''
                     */
                    _style:'',
                    set style(val) { this._style = val.toString(); },
                    get style() { return this._style; },

                    close: { //toast notification close container config options
                        /**
                         * @property {boolean} enabled - Indicates if the close button should be shown for toast notifications.
                         * @default true
                         */
                        _enabled: true,
                        set enabled(val) { this._enabled = !!val; },
                        get enabled() { return this._enabled; },

                        /**
                         * @property {string} style - Custom styling that is applied to the toast notification close
                         * container. Any styling defined here will override the defaults.
                         * @default ''
                         */
                        _style:'',
                        set style(val) { this._style = val.toString(); },
                        get style() { return this._style; }
                    }
                },

                native: { //native/desktop notification config options
                    /**
                     * @property {boolean} enabled - Indicates if native notifications should be enabled (still must be
                     * allowed by user in browser).
                     * @default true
                     */
                    _enabled: true,
                    set enabled(val) { this._enabled = !!val; },
                    get enabled() { return this._enabled; },

                    /**
                     * @property {Number} hideAfterMs - Time in milliseconds that the notification will be automatically
                     * hidden after shown (specify <=0 to never hide the notification).
                     * @default -1
                     */
                    _hideAfterMs: -1,
                    set hideAfterMs(val) {
                        if (typeof val === 'number') { this._hideAfterMs = Math.round(val); }
                        else { this._hideAfterMs = -1; }
                    },
                    get hideAfterMs() { return this._hideAfterMs; }
                }
            },

            //Events

            //Useful for setting config options, setting up event listeners, etc...
            /**
             * Fired after the library is initialized and listening for new notifications. This is the recommended place
             * to change default configuration options if the listener is guaranteed to exist before the library loads.
             * Only fires on initial load.
             * Not cancelable.
             * @event voyentNotifyInitialized
             */
            
            /**
             * Fired after a new notification is received in the browser. Not cancelable.
             * @event notificationReceived
             */

            //Useful for previewing the new item being added to the queue and throwing it away before it's added using preventDefault().
            /**
             * Fired before the queue is updated. An update will be triggered when loading a queue from
             * storage or adding, removing or clearing the queue. Cancel the event to prevent the operation.
             * @event beforeQueueUpdated
             */

            //Useful for keeping the various aspects of the app in sync (queue, notification count, etc...)
            /**
             * Fired after the queue is updated. Not cancelable.
             * @event afterQueueUpdated
             */

            //Useful for preventing the notification from being displayed using e.preventDefault().
            /**
             * Fired before a notification is displayed. Fires for both toast and browser native notifications.
             * Cancel the event to prevent the notification from being displayed.
             * @event beforeDisplayNotification
             */

            //Useful for custom CSS effects/styling
            /**
             * Fired after a notification is displayed. Fires for both toast and browser native notifications. Not cancelable.
             * @event afterDisplayNotification
             */

            //Useful to do custom handling on the selected notification. Especially after the asynchronous call of fetching a notification from the mailbox service.
            /**
             * Fired after the selected property is changed. The notification returned may be null. If this event fires
             * in relation to a queue update then this event will always be fired AFTER the queue has been updated. Not cancelable.
             * @event notificationChanged
             */

            //Useful for custom redirecting (for apps that use custom routing).
            /**
             * Fired when a notification is clicked. Fires for both toast and browser native notifications. Cancel the
             * event to prevent the app from redirecting to the URL specified in the notification.
             * @event notificationClicked
             */

            //Useful for custom close behaviour or preventing the notification from closing.
            /**
             * Fired when a notification is closed. Fires for both toast and browser native notifications. Cancel the event
             * to prevent the notification from closing automatically (toast notifications only).
             * @event notificationClosed
             */

            //Private/internal attributes
            _listener: null, //reference to the xio.push listener

            /**
             * Start listening for notifications. This function is called automatically when the library is loaded and a
             * user is logged in via voyent.io. This function only needs to be called manually after stopListening has
             * been called.
             */
            startListening: function() {
                if (this._listener) {
                    return;
                }

                var _this = this;
                //declare push listener
                this._listener = function (notification) {
                    if (!_parseNotification(notification)) {
                        return;
                    }
                    _fireEvent('notificationReceived',{"notification":notification},false);
                    var cancelled = _fireEvent('beforeQueueUpdated',{"op":"add","notification":notification,"queue":_this.queue.slice(0)},true);
                    if (cancelled) {
                        return;
                    }
                    _this._queue.push(notification);
                    _setQueueInStorage();
                    _fireEvent('afterQueueUpdated',{"op":"add","notification":notification,"queue":_this.queue.slice(0)},false);
                    _this.displayNotification(notification);
                    if (!_this.selected) {
                        //we don't have a selected notification so set to this new one
                        _this.selectNotification(notification);
                        //inject notification data into the page if they are on
                        //the relevant page and currently have no selected notification
                        var notificationUrl = document.createElement('a'); //use anchor tag to parse notification return URL
                        notificationUrl.href = notification.url;
                        if (window.location.host === notificationUrl.host &&
                            window.location.pathname === notificationUrl.pathname) {
                            _this.injectNotificationData();
                        }
                    }
                };
                //add push listener
                v.xio.push.removeListener(this._listener);
                v.xio.push.addListener(this._listener);
            },

            /**
             * Stop listening for notifications. No new notifications will be received by the library after calling this
             * function but the other features of the library will still be available.
             */
            stopListening: function() {
                v.xio.push.removeListener(this._listener);
                this._listener = null;
                //since they explicitly stopped listening them remove the login listener as well
                window.removeEventListener('voyent-login-succeeded',_setupListeners);
            },

            /**
             * Returns an integer that represents the number of notifications currently in the queue.
             * @returns {Number} - The notification count.
             */
            getNotificationCount: function() {
                return this.queue.length;
            },

            /**
             * Returns the notification at the specified index or null if none was found.
             * @param {number} index - The zero-based index of the notification in the queue.
             * @returns {Object} - The notification.
             */
            getNotificationAt: function(index) {
                return this.queue[index] || null;
            },

            /**
             * Returns the next (newer) notification in the queue or null if there is no next.
             * @returns {Object} - The notification.
             */
            getNextNotification: function() {
                var newPos = this._queuePosition+1;
                if (this.queue[newPos]) {
                    return this.queue[newPos];
                }
                return null;
            },

            /**
             * Returns the previous (older) notification in the queue or null if there is no previous.
             * @returns {Object} - The notification.
             */
            getPreviousNotification: function() {
                var newPos = this._queuePosition-1;
                if (this.queue[newPos]) {
                    return this.queue[newPos];
                }
                return null;
            },

            /**
             * Returns the newest notification by date that is currently in the queue or null if the queue is empty.
             * @returns {Object} - The notification.
             */
            getNewestNotification: function() {
                if (this.queue.length > 0) {
                    return this.queue[this.queue.length-1];
                }
                return null;
            },

            /**
             * Returns the oldest notification by date that is currently in the queue or null if the queue is empty.
             * @returns {Object} - The notification.
             */
            getOldestNotification: function() {
                if (this.queue.length > 0) {
                    return this.queue[0];
                }
                return null;
            },

            /**
             * Removes the specified notification from the queue.
             * @param {Object} notification - The notification to be removed.
             * @returns {boolean} - Indicates if the notification was removed successfully.
             */
            removeNotification: function(notification) {
                var index = this.queue.indexOf(notification);
                if (index > -1) {
                    var cancelled = _fireEvent('beforeQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},true);
                    if (cancelled) {
                        return false;
                    }
                    //if we have an id it means the notification is stored in the
                    //mailbox service so we will delete it from the user's mail
                    if (this.queue[index].notificationId) {
                        _removeNotificationFromMailbox(this.queue[index].notificationId);
                    }
                    this._queue.splice(index,1);
                    _setQueueInStorage();
                    _fireEvent('afterQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},false);
                    return true;
                }
                return false;
            },

            /**
             * Removes the notification from the queue at the specified index.
             * @param {number} index - The zero-based index of the notification in the queue.
             * @returns {boolean} - Indicates if the notification was removed successfully.
             */
            removeNotificationAt: function(index) {
                var notification = this.queue[index];
                if (notification) {
                    var cancelled = _fireEvent('beforeQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},true);
                    if (cancelled) {
                       return false;
                    }
                    //if we have an id it means the notification is stored in the
                    //mailbox service so we will delete it from the user's mail
                    if (notification.notificationId) {
                        _removeNotificationFromMailbox(notification.notificationId);
                    }
                    this._queue.splice(index,1);
                    _setQueueInStorage();
                    _fireEvent('afterQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},false);
                    return true;
                }
                return false;
            },

            /**
             * Removes the selected notification. If successful the queuePosition will be reset to -1 indicating that no 
             * notification is currently selected.
             * @returns {boolean} - Indicates if the notification was removed successfully.
             */
            removeSelectedNotification: function() {
                if (!this.selected) {
                    return false; //nothing to remove
                }
                var notification = this.queue[this._queuePosition];
                var cancelled = _fireEvent('beforeQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},true);
                if (cancelled) {
                    return false;
                }
                //remove the notification from the queue
                this._queue.splice(this._queuePosition,1);
                _setQueueInStorage();
                this._queuePosition = -1;
                _fireEvent('afterQueueUpdated',{"op":"del","notification":notification,"queue":this.queue.slice(0)},false);
                //reset the selected property
                //if we have an id it means the notification is stored in the
                //mailbox service so we will delete it from the user's mail
                if (this.selected.notificationId) {
                    _removeNotificationFromMailbox(this.selected.notificationId);
                }
                this._selected = null;
                _setSelectedNotificationInStorage();
                _fireEvent('notificationChanged',{"notification":this.selected},false);
                _autoSelectNotification();
                return true;
            },

            /**
             * Removes all notifications from the notification queue (including clearing the selected notification) and
             * resets the queuePosition to -1.
             */
            clearNotificationQueue: function() {
                if (!this.queue || this.queue.length === 0) {
                    return; //queue is already empty
                }
                var cancelled = _fireEvent('beforeQueueUpdated',{"op":"clear","queue":this.queue.slice(0)},true);
                if (cancelled) {
                    return;
                }
                for (var i=0; i<this.queue.length; i++) {
                    if (this.queue[i].notificationId) {
                        _removeNotificationFromMailbox(this.queue[i].notificationId);
                    }
                }
                this._queue = [];
                _setQueueInStorage();
                this._queuePosition = -1;
                _fireEvent('afterQueueUpdated',{"op":"clear","queue":this.queue.slice(0)},false);
                //clear the selected notification
                if (this._selected) {
                    this._selected = null;
                    _setSelectedNotificationInStorage();
                    _fireEvent('notificationChanged',{"notification":this.selected},false);
                }
            },

            /**
             * Redirects the browser to the URL specified in the passed notification and injects the notification data into the page.
             * @param {Object} notification - The notification that determines where to redirect.
             */
            redirectToNotification: function(notification) {
                if (!notification.url) {
                    return;
                }
                //save the notification to inject in session storage so it survives the redirect
                _setSelectedNotificationInStorage(notification);
                //redirect browser
                window.location.replace(notification.url);
            },

            /**
             * Injects the selected notification into elements with data-selected-* attributes.
             * Currently has special support for input and select elements. For all other elements the data
             * will be inserted as text content.
             */
            injectNotificationData: function() {
                _injectOrClearNotficationData(false);
            },

            /**
             * Removes all injected notification data from the page.
             */
            clearInjectedNotificationData: function() {
                _injectOrClearNotficationData(true);
            },

            /**
             * Sets the selected notification to the one passed if it is a valid notification in the queue.
             * @param {Object} notification - The notification object to be set.
             * @returns {boolean} - Indicates if the notification was set successfully.
             */
            selectNotification: function(notification) {
                var index = this.queue.indexOf(notification);
                if (index > -1 && this.selected !== notification) {
                    this._selected = notification;
                    this._queuePosition = index;
                    _setSelectedNotificationInStorage();
                    _fireEvent('notificationChanged',{"notification":this.selected},false);
                    return true;
                }
                return false;
            },

            /**
             * Sets the selected notification to the one in the queue at the specified index.
             * @param {number} index - The zero-based index of the notification in the queue.
             * @returns {boolean} - Indicates if the notification was set successfully.
             */
            selectNotificationAt: function(index) {
                var notification = this.queue[index];
                if (notification && notification !== this.selected) {
                    this._selected = notification;
                    this._queuePosition = index;
                    _setSelectedNotificationInStorage();
                    _fireEvent('notificationChanged',{"notification":this.selected},false);
                    return true;
                }
                return false;
            },

            /**
             * Displays the passed notification as a toast or browser native notification, depending on the current
             * configuration. Can be used to re-display a notification from the queue or even to display a custom
             * notification that is not part of the queue.
             * @param {object} notification - The notification to display.
             */
            displayNotification: function (notification) {
                if (notify.config.native.enabled && window.Notification && Notification.permission === 'granted') {
                    _displayNativeNotification(notification);
                }
                else if (notify.config.toast.enabled) {
                    _displayToastNotification(notification);
                }
            },

            /**
             * Manually hides a notification.
             * @param {Object} notification - A toast or browser native notification reference.
             * @param {number} ms - The number of milliseconds to wait before hiding the notification.
             */
            hideNotification: function(notification,ms) {
                if (!notification || !notification.constructor) {
                    return;
                }
                setTimeout(function() {
                    if (notification.constructor === HTMLDivElement) { //toast notification
{'fraction_non_alphanumeric': 0.09289885923257464, 'fraction_numerical': 0.002001346360278733, 'mean_word_length': 1.396093988404028, 'pattern_counts': {'":': 66, '<': 11, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1417165', 'n_tokens_mistral': 11846, 'n_tokens_neox': 10983, 'n_words': 3723}