<commit_before>/**
 * Kendo UI v2016.3.914 (http://www.telerik.com/kendo-ui)
 * Copyright 2016 Telerik AD. All rights reserved.
 *
 * Kendo UI commercial licenses may be obtained at
 * http://www.telerik.com/purchase/license-agreement/kendo-ui-complete
 * If you do not own a commercial license, this file shall be governed by the trial license terms.

*/
(function (f, define) {
    define('kendo.notification', [
        'kendo.core',
        'kendo.popup'
    ], f);
}(function () {
    var __meta__ = {
        id: 'notification',
        name: 'Notification',
        category: 'web',
        description: 'The Notification widget displays user alerts.',
        depends: [
            'core',
            'popup'
        ]
    };
    (function ($, undefined) {
        var kendo = window.kendo, Widget = kendo.ui.Widget, proxy = $.proxy, extend = $.extend, setTimeout = window.setTimeout, CLICK = 'click', SHOW = 'show', HIDE = 'hide', KNOTIFICATION = 'k-notification', KICLOSE = '.k-notification-wrap .k-i-close', KHIDING = 'k-hiding', INFO = 'info', SUCCESS = 'success', WARNING = 'warning', ERROR = 'error', TOP = 'top', LEFT = 'left', BOTTOM = 'bottom', RIGHT = 'right', UP = 'up', NS = '.kendoNotification', WRAPPER = '

', TEMPLATE = '
' + '#=typeIcon#' + '#=content#' + 'Hide' + '
', SAFE_TEMPLATE = TEMPLATE.replace('#=content#', '#:content#');
        var Notification = Widget.extend({
            init: function (element, options) {
                var that = this;
                Widget.fn.init.call(that, element, options);
                options = that.options;
                if (!options.appendTo || !$(options.appendTo).is(element)) {
                    that.element.hide();
                }
                that._compileTemplates(options.templates);
                that._guid = '_' + kendo.guid();
                that._isRtl = kendo.support.isRtl(element);
                that._compileStacking(options.stacking, options.position.top, options.position.left);
                kendo.notify(that);
            },
            events: [
                SHOW,
                HIDE
            ],
            options: {
                name: 'Notification',
                position: {
                    pinned: true,
                    top: null,
                    left: null,
                    bottom: 20,
                    right: 20
                },
                stacking: 'default',
                hideOnClick: true,
                button: false,
                allowHideAfter: 0,
                autoHideAfter: 5000,
                appendTo: null,
                width: null,
                height: null,
                templates: [],
                animation: {
                    open: {
                        effects: 'fade:in',
                        duration: 300
                    },
                    close: {
                        effects: 'fade:out',
                        duration: 600,
                        hide: true
                    }
                }
            },
            _compileTemplates: function (templates) {
                var that = this;
                var kendoTemplate = kendo.template;
                that._compiled = {};
                $.each(templates, function (key, value) {
                    that._compiled[value.type] = kendoTemplate(value.template || $('#' + value.templateId).html());
                });
                that._defaultCompiled = kendoTemplate(TEMPLATE);
                that._safeCompiled = kendoTemplate(SAFE_TEMPLATE);
            },
            _getCompiled: function (type, safe) {
                var defaultCompiled = safe ? this._safeCompiled : this._defaultCompiled;
                return type ? this._compiled[type] || defaultCompiled : defaultCompiled;
            },
            _compileStacking: function (stacking, top, left) {
                var that = this, paddings = {
                        paddingTop: 0,
                        paddingRight: 0,
                        paddingBottom: 0,
                        paddingLeft: 0
                    }, horizontalAlignment = left !== null ? LEFT : RIGHT, origin, position;
                switch (stacking) {
                case 'down':
                    origin = BOTTOM + ' ' + horizontalAlignment;
                    position = TOP + ' ' + horizontalAlignment;
                    delete paddings.paddingBottom;
                    break;
                case RIGHT:
                    origin = TOP + ' ' + RIGHT;
                    position = TOP + ' ' + LEFT;
                    delete paddings.paddingRight;
                    break;
                case LEFT:
                    origin = TOP + ' ' + LEFT;
                    position = TOP + ' ' + RIGHT;
                    delete paddings.paddingLeft;
                    break;
                case UP:
                    origin = TOP + ' ' + horizontalAlignment;
                    position = BOTTOM + ' ' + horizontalAlignment;
                    delete paddings.paddingTop;
                    break;
                default:
                    if (top !== null) {
                        origin = BOTTOM + ' ' + horizontalAlignment;
                        position = TOP + ' ' + horizontalAlignment;
                        delete paddings.paddingBottom;
                    } else {
                        origin = TOP + ' ' + horizontalAlignment;
                        position = BOTTOM + ' ' + horizontalAlignment;
                        delete paddings.paddingTop;
                    }
                    break;
                }
                that._popupOrigin = origin;
                that._popupPosition = position;
                that._popupPaddings = paddings;
            },
            _attachPopupEvents: function (options, popup) {
                var that = this, allowHideAfter = options.allowHideAfter, attachDelay = !isNaN(allowHideAfter) && allowHideAfter > 0, closeIcon;
                function attachClick(target) {
                    target.on(CLICK + NS, function () {
                        that._hidePopup(popup);
                    });
                }
                if (popup.options.anchor !== document.body && popup.options.origin.indexOf(RIGHT) > 0) {
                    popup.bind('open', function () {
                        var shadows = kendo.getShadows(popup.element);
                        setTimeout(function () {
                            popup.wrapper.css('left', parseFloat(popup.wrapper.css('left')) + shadows.left + shadows.right);
                        });
                    });
                }
                if (options.hideOnClick) {
                    popup.bind('activate', function () {
                        if (attachDelay) {
                            setTimeout(function () {
                                attachClick(popup.element);
                            }, allowHideAfter);
                        } else {
                            attachClick(popup.element);
                        }
                    });
                } else if (options.button) {
                    closeIcon = popup.element.find(KICLOSE);
                    if (attachDelay) {
                        setTimeout(function () {
                            attachClick(closeIcon);
                        }, allowHideAfter);
                    } else {
                        attachClick(closeIcon);
                    }
                }
            },
            _showPopup: function (wrapper, options) {
                var that = this, autoHideAfter = options.autoHideAfter, x = options.position.left, y = options.position.top, popup, openPopup;
                openPopup = $('.' + that._guid + ':not(.' + KHIDING + ')').last();
                popup = new kendo.ui.Popup(wrapper, {
                    anchor: openPopup[0] ? openPopup : document.body,
                    origin: that._popupOrigin,
                    position: that._popupPosition,
                    animation: options.animation,
                    modal: true,
                    collision: '',
                    isRtl: that._isRtl,
                    close: function () {
                        that._triggerHide(this.element);
                    },
                    deactivate: function (e) {
                        e.sender.element.off(NS);
                        e.sender.element.find(KICLOSE).off(NS);
                        e.sender.destroy();
                    }
                });
                that._attachPopupEvents(options, popup);
                if (openPopup[0]) {
                    popup.open();
                } else {
                    if (x === null) {
                        x = $(window).width() - wrapper.width() - options.position.right;
                    }
                    if (y === null) {
                        y = $(window).height() - wrapper.height() - options.position.bottom;
                    }
                    popup.open(x, y);
                }
                popup.wrapper.addClass(that._guid).css(extend({ margin: 0 }, that._popupPaddings));
                if (options.position.pinned) {
                    popup.wrapper.css('position', 'fixed');
                    if (openPopup[0]) {
                        that._togglePin(popup.wrapper, true);
                    }
                } else if (!openPopup[0]) {
                    that._togglePin(popup.wrapper, false);
                }
                if (autoHideAfter > 0) {
                    setTimeout(function () {
                        that._hidePopup(popup);
                    }, autoHideAfter);
                }
            },
            _hidePopup: function (popup) {
                popup.wrapper.addClass(KHIDING);
                popup.close();
            },
            _togglePin: function (wrapper, pin) {
                var win = $(window), sign = pin ? -1 : 1;
                wrapper.css({
                    top: parseInt(wrapper.css(TOP), 10) + sign * win.scrollTop(),
                    left: parseInt(wrapper.css(LEFT), 10) + sign * win.scrollLeft()
                });
            },
            _attachStaticEvents: function (options, wrapper) {
                var that = this, allowHideAfter = options.allowHideAfter, attachDelay = !isNaN(allowHideAfter) && allowHideAfter > 0;
                function attachClick(target) {
                    target.on(CLICK + NS, proxy(that._hideStatic, that, wrapper));
                }
                if (options.hideOnClick) {
                    if (attachDelay) {
                        setTimeout(function () {
                            attachClick(wrapper);
                        }, allowHideAfter);
                    } else {
                        attachClick(wrapper);
                    }
                } else if (options.button) {
                    if (attachDelay) {
                        setTimeout(function () {
                            attachClick(wrapper.find(KICLOSE));
                        }, allowHideAfter);
                    } else {
                        attachClick(wrapper.find(KICLOSE));
                    }
                }
            },
            _showStatic: function (wrapper, options) {
                var that = this, autoHideAfter = options.autoHideAfter, animation = options.animation, insertionMethod = options.stacking == UP || options.stacking == LEFT ? 'prependTo' : 'appendTo';
                wrapper.addClass(that._guid)[insertionMethod](options.appendTo).hide().kendoAnimate(animation.open || false);
                that._attachStaticEvents(options, wrapper);
                if (autoHideAfter > 0) {
                    setTimeout(function () {
                        that._hideStatic(wrapper);
                    }, autoHideAfter);
                }
            },
            _hideStatic: function (wrapper) {
                wrapper.kendoAnimate(extend(this.options.animation.close || false, {
                    complete: function () {
                        wrapper.off(NS).find(KICLOSE).off(NS);
                        wrapper.remove();
                    }
                }));
                this._triggerHide(wrapper);
            },
            _triggerHide: function (element) {
                this.trigger(HIDE, { element: element });
                this.angular('cleanup', function () {
                    return { elements: element };
                });
            },
            show: function (content, type, safe) {
                var that = this, options = that.options, wrapper = $(WRAPPER), args, defaultArgs;
                if (!type) {
                    type = INFO;
                }
                if (content !== null && content !== undefined && content !== '') {
                    if (kendo.isFunction(content)) {
                        content = content();
                    }
                    defaultArgs = {
                        typeIcon: type,
                        content: ''
                    };
                    if ($.isPlainObject(content)) {
                        args = extend(defaultArgs, content);
                    } else {
                        args = extend(defaultArgs, { content: content });
                    }
                    wrapper.addClass(KNOTIFICATION + '-' + type).toggleClass(KNOTIFICATION + '-button', options.button).attr('data-role', 'alert').css({
                        width: options.width,
                        height: options.height
                    }).append(that._getCompiled(type, safe)(args));
                    that.angular('compile', function () {
                        return {
                            elements: wrapper,
                            data: [{ dataItem: args }]
                        };
                    });
                    if ($(options.appendTo)[0]) {
                        that._showStatic(wrapper, options);
                    } else {
                        that._showPopup(wrapper, options);
                    }
                    that.trigger(SHOW, { element: wrapper });
                }
                return that;
            },
            showText: function (content, type) {
                this.show(content, type, true);
            },
            info: function (content) {
                return this.show(content, INFO);
            },
            success: function (content) {
                return this.show(content, SUCCESS);
            },
            warning: function (content) {
                return this.show(content, WARNING);
            },
            error: function (content) {
                return this.show(content, ERROR);
            },
            hide: function () {
                var that = this, openedNotifications = that.getNotifications();
                if (that.options.appendTo) {
                    openedNotifications.each(function (idx, element) {
                        that._hideStatic($(element));
                    });
                } else {
                    openedNotifications.each(function (idx, element) {
                        var popup = $(element).data('kendoPopup');
                        if (popup) {
                            that._hidePopup(popup);
                        }
                    });
                }
                return that;
            },
            getNotifications: function () {
                var that = this, guidElements = $('.' + that._guid + ':not(.' + KHIDING + ')');
                if (that.options.appendTo) {
                    return guidElements;
                } else {
                    return guidElements.children('.' + KNOTIFICATION);
                }
            },
            setOptions: function (newOptions) {
                var that = this, options;
                Widget.fn.setOptions.call(that, newOptions);
                options = that.options;
                if (newOptions.templates !== undefined) {
                    that._compileTemplates(options.templates);
                }
                if (newOptions.stacking !== undefined || newOptions.position !== undefined) {
                    that._compileStacking(options.stacking, options.position.top, options.position.left);
                }
            },
            destroy: function () {
                Widget.fn.destroy.call(this);
                this.getNotifications().off(NS).find(KICLOSE).off(NS);
            }
        });
        kendo.ui.plugin(Notification);
    }(window.kendo.jQuery));
    return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
    (a3 || a2)();
}));<commit_msg>Fixed z-index<commit_after>/**
 * Kendo UI v2016.3.914 (http://www.telerik.com/kendo-ui)
 * Copyright 2016 Telerik AD. All rights reserved.
 *
 * Kendo UI commercial licenses may be obtained at
 * http://www.telerik.com/purchase/license-agreement/kendo-ui-complete
 * If you do not own a commercial license, this file shall be governed by the trial license terms.

*/
(function (f, define) {
    define('kendo.notification', [
        'kendo.core',
        'kendo.popup'
    ], f);
}(function () {
    var __meta__ = {
        id: 'notification',
        name: 'Notification',
        category: 'web',
        description: 'The Notification widget displays user alerts.',
        depends: [
            'core',
            'popup'
        ]
    };
    (function ($, undefined) {
        var kendo = window.kendo, Widget = kendo.ui.Widget, proxy = $.proxy, extend = $.extend, setTimeout = window.setTimeout, CLICK = 'click', SHOW = 'show', HIDE = 'hide', KNOTIFICATION = 'k-notification', KICLOSE = '.k-notification-wrap .k-i-close', KHIDING = 'k-hiding', INFO = 'info', SUCCESS = 'success', WARNING = 'warning', ERROR = 'error', TOP = 'top', LEFT = 'left', BOTTOM = 'bottom', RIGHT = 'right', UP = 'up', NS = '.kendoNotification', WRAPPER = '

', TEMPLATE = '
' + '#=typeIcon#' + '#=content#' + 'Hide' + '
', SAFE_TEMPLATE = TEMPLATE.replace('#=content#', '#:content#');
        var Notification = Widget.extend({
            init: function (element, options) {
                var that = this;
                Widget.fn.init.call(that, element, options);
                options = that.options;
                if (!options.appendTo || !$(options.appendTo).is(element)) {
                    that.element.hide();
                }
                that._compileTemplates(options.templates);
                that._guid = '_' + kendo.guid();
                that._isRtl = kendo.support.isRtl(element);
                that._compileStacking(options.stacking, options.position.top, options.position.left);
                kendo.notify(that);
            },
            events: [
                SHOW,
                HIDE
            ],
            options: {
                name: 'Notification',
                position: {
                    pinned: true,
                    top: null,
                    left: null,
                    bottom: 20,
                    right: 20
                },
                stacking: 'default',
                hideOnClick: true,
                button: false,
                allowHideAfter: 0,
                autoHideAfter: 5000,
                appendTo: null,
                width: null,
                height: null,
                templates: [],
                animation: {
                    open: {
                        effects: 'fade:in',
                        duration: 300
                    },
                    close: {
                        effects: 'fade:out',
                        duration: 600,
                        hide: true
                    }
                }
            },
            _compileTemplates: function (templates) {
                var that = this;
                var kendoTemplate = kendo.template;
                that._compiled = {};
                $.each(templates, function (key, value) {
                    that._compiled[value.type] = kendoTemplate(value.template || $('#' + value.templateId).html());
                });
                that._defaultCompiled = kendoTemplate(TEMPLATE);
                that._safeCompiled = kendoTemplate(SAFE_TEMPLATE);
            },
            _getCompiled: function (type, safe) {
                var defaultCompiled = safe ? this._safeCompiled : this._defaultCompiled;
                return type ? this._compiled[type] || defaultCompiled : defaultCompiled;
            },
            _compileStacking: function (stacking, top, left) {
                var that = this, paddings = {
                        paddingTop: 0,
                        paddingRight: 0,
                        paddingBottom: 0,
                        paddingLeft: 0
                    }, horizontalAlignment = left !== null ? LEFT : RIGHT, origin, position;
                switch (stacking) {
                case 'down':
                    origin = BOTTOM + ' ' + horizontalAlignment;
                    position = TOP + ' ' + horizontalAlignment;
                    delete paddings.paddingBottom;
                    break;
                case RIGHT:
                    origin = TOP + ' ' + RIGHT;
                    position = TOP + ' ' + LEFT;
                    delete paddings.paddingRight;
                    break;
                case LEFT:
                    origin = TOP + ' ' + LEFT;
                    position = TOP + ' ' + RIGHT;
                    delete paddings.paddingLeft;
                    break;
                case UP:
                    origin = TOP + ' ' + horizontalAlignment;
                    position = BOTTOM + ' ' + horizontalAlignment;
                    delete paddings.paddingTop;
                    break;
                default:
                    if (top !== null) {
                        origin = BOTTOM + ' ' + horizontalAlignment;
                        position = TOP + ' ' + horizontalAlignment;
                        delete paddings.paddingBottom;
                    } else {
                        origin = TOP + ' ' + horizontalAlignment;
                        position = BOTTOM + ' ' + horizontalAlignment;
                        delete paddings.paddingTop;
                    }
                    break;
                }
                that._popupOrigin = origin;
                that._popupPosition = position;
                that._popupPaddings = paddings;
            },
            _attachPopupEvents: function (options, popup) {
                var that = this, allowHideAfter = options.allowHideAfter, attachDelay = !isNaN(allowHideAfter) && allowHideAfter > 0, closeIcon;
                function attachClick(target) {
                    target.on(CLICK + NS, function () {
                        that._hidePopup(popup);
                    });
                }
                if (popup.options.anchor !== document.body && popup.options.origin.indexOf(RIGHT) > 0) {
                    popup.bind('open', function () {
                        var shadows = kendo.getShadows(popup.element);
                        setTimeout(function () {
                            popup.wrapper.css('left', parseFloat(popup.wrapper.css('left')) + shadows.left + shadows.right);
                        });
                    });
                }
                if (options.hideOnClick) {
                    popup.bind('activate', function () {
                        if (attachDelay) {
                            setTimeout(function () {
                                attachClick(popup.element);
                            }, allowHideAfter);
                        } else {
                            attachClick(popup.element);
                        }
                    });
                } else if (options.button) {
                    closeIcon = popup.element.find(KICLOSE);
                    if (attachDelay) {
                        setTimeout(function () {
                            attachClick(closeIcon);
                        }, allowHideAfter);
                    } else {
                        attachClick(closeIcon);
                    }
                }
            },
            _showPopup: function (wrapper, options) {
                var that = this, autoHideAfter = options.autoHideAfter, x = options.position.left, y = options.position.top, popup, openPopup;
                openPopup = $('.' + that._guid + ':not(.' + KHIDING + ')').last();
                popup = new kendo.ui.Popup(wrapper, {
                    anchor: openPopup[0] ? openPopup : document.body,
                    origin: that._popupOrigin,
                    position: that._popupPosition,
                    animation: options.animation,
                    modal: true,
                    collision: '',
                    isRtl: that._isRtl,
                    close: function () {
                        that._triggerHide(this.element);
                    },
                    deactivate: function (e) {
                        e.sender.element.off(NS);
                        e.sender.element.find(KICLOSE).off(NS);
                        e.sender.destroy();
                    }
                });
                that._attachPopupEvents(options, popup);
                if (openPopup[0]) {
                    popup.open();
                } else {
                    if (x === null) {
                        x = $(window).width() - wrapper.width() - options.position.right;
                    }
                    if (y === null) {
                        y = $(window).height() - wrapper.height() - options.position.bottom;
                    }
                    popup.open(x, y);
                }
                popup.wrapper.addClass(that._guid).css(extend({ margin: 0, zIndex: 10050 }, that._popupPaddings));
                if (options.position.pinned) {
                    popup.wrapper.css('position', 'fixed');
                    if (openPopup[0]) {
                        that._togglePin(popup.wrapper, true);
                    }
                } else if (!openPopup[0]) {
                    that._togglePin(popup.wrapper, false);
                }
                if (autoHideAfter > 0) {
                    setTimeout(function () {
                        that._hidePopup(popup);
                    }, autoHideAfter);
                }
            },
            _hidePopup: function (popup) {
                popup.wrapper.addClass(KHIDING);
                popup.close();
            },
            _togglePin: function (wrapper, pin) {
                var win = $(window), sign = pin ? -1 : 1;
                wrapper.css({
                    top: parseInt(wrapper.css(TOP), 10) + sign * win.scrollTop(),
                    left: parseInt(wrapper.css(LEFT), 10) + sign * win.scrollLeft()
                });
            },
            _attachStaticEvents: function (options, wrapper) {
                var that = this, allowHideAfter = options.allowHideAfter, attachDelay = !isNaN(allowHideAfter) && allowHideAfter > 0;
                function attachClick(target) {
                    target.on(CLICK + NS, proxy(that._hideStatic, that, wrapper));
                }
                if (options.hideOnClick) {
                    if (attachDelay) {
                        setTimeout(function () {
                            attachClick(wrapper);
                        }, allowHideAfter);
                    } else {
                        attachClick(wrapper);
                    }
                } else if (options.button) {
                    if (attachDelay) {
                        setTimeout(function () {
                            attachClick(wrapper.find(KICLOSE));
                        }, allowHideAfter);
                    } else {
                        attachClick(wrapper.find(KICLOSE));
                    }
                }
            },
            _showStatic: function (wrapper, options) {
                var that = this, autoHideAfter = options.autoHideAfter, animation = options.animation, insertionMethod = options.stacking == UP || options.stacking == LEFT ? 'prependTo' : 'appendTo';
                wrapper.addClass(that._guid)[insertionMethod](options.appendTo).hide().kendoAnimate(animation.open || false);
                that._attachStaticEvents(options, wrapper);
                if (autoHideAfter > 0) {
                    setTimeout(function () {
                        that._hideStatic(wrapper);
                    }, autoHideAfter);
                }
            },
            _hideStatic: function (wrapper) {
                wrapper.kendoAnimate(extend(this.options.animation.close || false, {
                    complete: function () {
                        wrapper.off(NS).find(KICLOSE).off(NS);
                        wrapper.remove();
                    }
                }));
                this._triggerHide(wrapper);
            },
            _triggerHide: function (element) {
                this.trigger(HIDE, { element: element });
                this.angular('cleanup', function () {
                    return { elements: element };
                });
            },
            show: function (content, type, safe) {
                var that = this, options = that.options, wrapper = $(WRAPPER), args, defaultArgs;
                if (!type) {
                    type = INFO;
                }
                if (content !== null && content !== undefined && content !== '') {
                    if (kendo.isFunction(content)) {
                        content = content();
                    }
                    defaultArgs = {
                        typeIcon: type,
                        content: ''
                    };
                    if ($.isPlainObject(content)) {
                        args = extend(defaultArgs, content);
                    } else {
                        args = extend(defaultArgs, { content: content });
                    }
                    wrapper.addClass(KNOTIFICATION + '-' + type).toggleClass(KNOTIFICATION + '-button', options.button).attr('data-role', 'alert').css({
                        width: options.width,
                        height: options.height
                    }).append(that._getCompiled(type, safe)(args));
                    that.angular('compile', function () {
                        return {
                            elements: wrapper,
                            data: [{ dataItem: args }]
                        };
                    });
                    if ($(options.appendTo)[0]) {
                        that._showStatic(wrapper, options);
                    } else {
                        that._showPopup(wrapper, options);
                    }
                    that.trigger(SHOW, { element: wrapper });
                }
                return that;
            },
            showText: function (content, type) {
                this.show(content, type, true);
            },
            info: function (content) {
                return this.show(content, INFO);
            },
            success: function (content) {
                return this.show(content, SUCCESS);
            },
            warning: function (content) {
                return this.show(content, WARNING);
            },
            error: function (content) {
                return this.show(content, ERROR);
            },
            hide: function () {
                var that = this, openedNotifications = that.getNotifications();
                if (that.options.appendTo) {
                    openedNotifications.each(function (idx, element) {
                        that._hideStatic($(element));
                    });
                } else {
                    openedNotifications.each(function (idx, element) {
                        var popup = $(element).data('kendoPopup');
                        if (popup) {
                            that._hidePopup(popup);
                        }
                    });
                }
                return that;
            },
            getNotifications: function () {
                var that = this, guidElements = $('.' + that._guid + ':not(.' + KHIDING + ')');
                if (that.options.appendTo) {
                    return guidElements;
                } else {
                    return guidElements.children('.' + KNOTIFICATION);
                }
            },
            setOptions: function (newOptions) {
                var that = this, options;
                Widget.fn.setOptions.call(that, newOptions);
                options = that.options;
                if (newOptions.templates !== undefined) {
                    that._compileTemplates(options.templates);
                }
                if (newOptions.stacking !== undefined || newOptions.position !== undefined) {
                    that._compileStacking(options.stacking, options.position.top, options.position.left);
                }
            },
            destroy: function () {
                Widget.fn.destroy.call(this);
                this.getNotifications().off(NS).find(KICLOSE).off(NS);
            }
        });
        kendo.ui.plugin(Notification);
    }(window.kendo.jQuery));
    return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
    (a3 || a2)();
}));
<|endoftext|>


<commit_before>/*
 *   处理特殊键的兼容性问题
 */
UE.plugins['keystrokes'] = function() {
    var me = this;

    me.addListener('keydown', function(type, evt) {
        var keyCode = evt.keyCode || evt.which,
            rng = me.selection.getRange();

        //处理全选的情况
        if(!rng.collapsed && !(evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey || keyCode == 9 )){

            var tmpNode = rng.startContainer;
            if(domUtils.isFillChar(tmpNode)){
                rng.setStartBefore(tmpNode)
            }
            tmpNode = rng.endContainer;
            if(domUtils.isFillChar(tmpNode)){
                rng.setEndAfter(tmpNode)
            }
            rng.txtToElmBoundary();
            //结束边界可能放到了br的前边，要把br包含进来
            // x[xxx]

            if(rng.endContainer && rng.endContainer.nodeType == 1){
                tmpNode = rng.endContainer.childNodes[rng.endOffset];
                if(tmpNode && domUtils.isBr(tmpNode)){
                    rng.setEndAfter(tmpNode);
                }
            }
            if(rng.startOffset == 0){
                tmpNode = rng.startContainer;
                if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){
                    tmpNode = rng.endContainer;
                    if(rng.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){
                        me.fireEvent('saveScene');
                        me.body.innerHTML = '
'+(browser.ie ? '' : '
')+'
';
                        rng.setStart(me.body.firstChild,0).setCursor(false,true);
                        browser.ie && me._selectionChange();
                        domUtils.preventDefault(evt);
                        return;
                    }
                }
            }
        }

        //处理backspace/del
        if (keyCode == 8) {//|| keyCode == 46
            var start,end;
            //避免按两次删除才能生效的问题
            if(rng.inFillChar()){
                start = rng.startContainer;
                rng.setStartBefore(start).shrinkBoundary(true).collapse(true);
                if(start.nodeValue.length == 1){
                }else{
                    start.nodeValue = start.nodeValue.replace(new RegExp('^' + domUtils.fillChar),'');
                }
            }

            //解决选中control元素不能删除的问题
            if (start = rng.getClosedNode()) {
                me.fireEvent('saveScene');
                rng.setStartBefore(start);
                domUtils.remove(start);
                rng.setCursor();
                me.fireEvent('saveScene');
                domUtils.preventDefault(evt);
                return;
            }
            //阻止在table上的删除
            if (!browser.ie) {
                start = domUtils.findParentByTagName(rng.startContainer, 'table', true);
                end = domUtils.findParentByTagName(rng.endContainer, 'table', true);
                if (start && !end || !start && end || start !== end) {
                    evt.preventDefault();
                    return;
                }
            }
        }
        //处理tab键的逻辑
        if (keyCode == 9) {
            //不处理以下标签
            var excludeTagNameForTabKey = {
                'ol' : 1,
                'ul' : 1,
                'table':1
            };
            //处理组件里的tab按下事件
            if(me.fireEvent('tabkeydown')){
                domUtils.preventDefault(evt);
                return;
            }
            range = me.selection.getRange();
            me.fireEvent('saveScene');
            for (var i = 0,txt = '',tabSize = me.options.tabSize|| 4,tabNode =  me.options.tabNode || ' '; i < tabSize; i++) {
                txt += tabNode;
            }
            var span = me.document.createElement('span');
            span.innerHTML = txt + domUtils.fillChar;
            if (range.collapsed) {
                range.insertNode(span.cloneNode(true).firstChild).setCursor(true);
            } else {
                //普通的情况
                start = domUtils.findParent(range.startContainer, filterFn);
                end = domUtils.findParent(range.endContainer, filterFn);
                if (start && end && start === end) {
                    range.deleteContents();
                    range.insertNode(span.cloneNode(true).firstChild).setCursor(true);
                } else {
                    var bookmark = range.createBookmark(),
                        filterFn = function(node) {
                            return domUtils.isBlockElm(node) && !excludeTagNameForTabKey[node.tagName.toLowerCase()]

                        };
                    range.enlarge(true);
                    var bookmark2 = range.createBookmark(),
                        current = domUtils.getNextDomNode(bookmark2.start, false, filterFn);
                    while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {
                        current.insertBefore(span.cloneNode(true).firstChild, current.firstChild);
                        current = domUtils.getNextDomNode(current, false, filterFn);
                    }
                    range.moveToBookmark(bookmark2).moveToBookmark(bookmark).select();
                }
            }
            domUtils.preventDefault(evt)
        }
        //trace:1634
        //ff的del键在容器空的时候，也会删除
        if(browser.gecko && keyCode == 46){
            range = me.selection.getRange();
            if(range.collapsed){
                start = range.startContainer;
                if(domUtils.isEmptyBlock(start)){
                    var parent = start.parentNode;
                    while(domUtils.getChildCount(parent) == 1 && !domUtils.isBody(parent)){
                        start = parent;
                        parent = parent.parentNode;
                    }
                    if(start === parent.lastChild)
                        evt.preventDefault();
                    return;
                }
            }
        }
    });
    me.addListener('keyup', function(type, evt) {
        var keyCode = evt.keyCode || evt.which,
            rng;
        if(keyCode == 8){
            rng = me.selection.getRange();
            //处理当删除到body时，要重新给p标签展位
            if(rng.collapsed && domUtils.isBody(rng.startContainer)){
                var tmpNode = domUtils.createElement(me.document,'p',{
                    'innerHTML' : browser.ie ? domUtils.fillChar : '
'
                });
                rng.insertNode(tmpNode).setStart(tmpNode,0).setCursor(false,true);
            }
//            //chrome下如果删除了inline标签，浏览器会有记忆，在输入文字还是会套上刚才删除的标签，所以这里再选一次就不会了
            if(browser.chrome && rng.collapsed && rng.startContainer.nodeType == 1 && domUtils.isEmptyBlock(rng.startContainer)){
                rng.select(true);
            }
        }
    })
};<commit_msg>by zhanyi<commit_after>/*
 *   处理特殊键的兼容性问题
 */
UE.plugins['keystrokes'] = function() {
    var me = this;

    me.addListener('keydown', function(type, evt) {
        var keyCode = evt.keyCode || evt.which,
            rng = me.selection.getRange();

        //处理全选的情况
        if(!rng.collapsed && !(evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey || keyCode == 9 )){

            var tmpNode = rng.startContainer;
            if(domUtils.isFillChar(tmpNode)){
                rng.setStartBefore(tmpNode)
            }
            tmpNode = rng.endContainer;
            if(domUtils.isFillChar(tmpNode)){
                rng.setEndAfter(tmpNode)
            }
            rng.txtToElmBoundary();
            //结束边界可能放到了br的前边，要把br包含进来
            // x[xxx]

            if(rng.endContainer && rng.endContainer.nodeType == 1){
                tmpNode = rng.endContainer.childNodes[rng.endOffset];
                if(tmpNode && domUtils.isBr(tmpNode)){
                    rng.setEndAfter(tmpNode);
                }
            }
            if(rng.startOffset == 0){
                tmpNode = rng.startContainer;
                if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){
                    tmpNode = rng.endContainer;
                    if(rng.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){
                        me.fireEvent('saveScene');
                        me.body.innerHTML = '
'+(browser.ie ? '' : '
')+'
';
                        rng.setStart(me.body.firstChild,0).setCursor(false,true);
                        browser.ie && me._selectionChange();
                        domUtils.preventDefault(evt);
                        return;
                    }
                }
            }
        }

        //处理backspace/del
        if (keyCode == 8) {//|| keyCode == 46
            var start,end;
            //避免按两次删除才能生效的问题
            if(rng.inFillChar()){
                start = rng.startContainer;
                rng.setStartBefore(start).shrinkBoundary(true).collapse(true);
                if(domUtils.isFillChar(start)){
                    domUtils.remove(start)
                }else{
                    start.nodeValue = start.nodeValue.replace(new RegExp('^' + domUtils.fillChar ),'');
                }
            }

            //解决选中control元素不能删除的问题
            if (start = rng.getClosedNode()) {
                me.fireEvent('saveScene');
                rng.setStartBefore(start);
                domUtils.remove(start);
                rng.setCursor();
                me.fireEvent('saveScene');
                domUtils.preventDefault(evt);
                return;
            }
            //阻止在table上的删除
            if (!browser.ie) {
                start = domUtils.findParentByTagName(rng.startContainer, 'table', true);
                end = domUtils.findParentByTagName(rng.endContainer, 'table', true);
                if (start && !end || !start && end || start !== end) {
                    evt.preventDefault();
                    return;
                }
            }
        }
        //处理tab键的逻辑
        if (keyCode == 9) {
            //不处理以下标签
            var excludeTagNameForTabKey = {
                'ol' : 1,
                'ul' : 1,
                'table':1
            };
            //处理组件里的tab按下事件
            if(me.fireEvent('tabkeydown')){
                domUtils.preventDefault(evt);
                return;
            }
            range = me.selection.getRange();
            me.fireEvent('saveScene');
            for (var i = 0,txt = '',tabSize = me.options.tabSize|| 4,tabNode =  me.options.tabNode || ' '; i < tabSize; i++) {
                txt += tabNode;
            }
            var span = me.document.createElement('span');
            span.innerHTML = txt + domUtils.fillChar;
            if (range.collapsed) {
                range.insertNode(span.cloneNode(true).firstChild).setCursor(true);
            } else {
                //普通的情况
                start = domUtils.findParent(range.startContainer, filterFn);
                end = domUtils.findParent(range.endContainer, filterFn);
                if (start && end && start === end) {
                    range.deleteContents();
                    range.insertNode(span.cloneNode(true).firstChild).setCursor(true);
                } else {
                    var bookmark = range.createBookmark(),
                        filterFn = function(node) {
                            return domUtils.isBlockElm(node) && !excludeTagNameForTabKey[node.tagName.toLowerCase()]

                        };
                    range.enlarge(true);
                    var bookmark2 = range.createBookmark(),
                        current = domUtils.getNextDomNode(bookmark2.start, false, filterFn);
                    while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {
                        current.insertBefore(span.cloneNode(true).firstChild, current.firstChild);
                        current = domUtils.getNextDomNode(current, false, filterFn);
                    }
                    range.moveToBookmark(bookmark2).moveToBookmark(bookmark).select();
                }
            }
            domUtils.preventDefault(evt)
        }
        //trace:1634
        //ff的del键在容器空的时候，也会删除
        if(browser.gecko && keyCode == 46){
            range = me.selection.getRange();
            if(range.collapsed){
                start = range.startContainer;
                if(domUtils.isEmptyBlock(start)){
                    var parent = start.parentNode;
                    while(domUtils.getChildCount(parent) == 1 && !domUtils.isBody(parent)){
                        start = parent;
                        parent = parent.parentNode;
                    }
                    if(start === parent.lastChild)
                        evt.preventDefault();
                    return;
                }
            }
        }
    });
    me.addListener('keyup', function(type, evt) {
        var keyCode = evt.keyCode || evt.which,
            rng;
        if(keyCode == 8){
            rng = me.selection.getRange();
            //处理当删除到body时，要重新给p标签展位
            if(rng.collapsed && domUtils.isBody(rng.startContainer)){
                var tmpNode = domUtils.createElement(me.document,'p',{
                    'innerHTML' : browser.ie ? domUtils.fillChar : '
'
                });
                rng.insertNode(tmpNode).setStart(tmpNode,0).setCursor(false,true);
            }
//            //chrome下如果删除了inline标签，浏览器会有记忆，在输入文字还是会套上刚才删除的标签，所以这里再选一次就不会了
            if(browser.chrome && rng.collapsed && rng.startContainer.nodeType == 1 && domUtils.isEmptyBlock(rng.startContainer)){
                rng.select(true);
            }
        }
    })
};<|endoftext|>


<commit_before><commit_msg>添加getEnumName方法<commit_after><|endoftext|>


<commit_before>{
	"%d unread message": {
		"one": "%d unread message",
		"other": "%d unread messages"
	},
	"Unread messages in %%s, %%s and one other": {
		"one": "Unread messages in %%s, %%s and one other",
		"other": "Unread messages in %%s, %%s and %d others"
	},
	"Unread messages in %s": "Unread messages in %s",
	"Unread messages in %s and %s": "Unread messages in %s and %s",
	"Unread messages from %s": "Unread messages from %s",
	"Unread messages from %s and %s": "Unread messages from %s and %s",
	"Unread messages from %%s, %%s and one other": {
		"one": "Unread messages from %%s, %%s and one other",
		"other": "Unread messages from %%s, %%s and %d others"
	}
}
<commit_msg>Created Polish translation<commit_after>{
	"%d unread message": {
		"one": "%d nieprzeczytana wiadomość",
		"other": "%d nieprzeczytanych wiadomości"
	},
	"Unread messages in %%s, %%s and one other": {
		"one": "Nieprzeczytane wiadomości w %%s, %%s i na jeszcze jednym kanale",
		"other": "Nieprzeczytane wiadomości w %%s, %%s i na %d innych kanałach"
	},
	"Unread messages in %s": "Nieprzeczytane wiadmomości w %s",
	"Unread messages in %s and %s": "Nieprzeczytane wiadomości w %s i %s",
	"Unread messages from %s": "Nieprzeczytane wiadomości od %s",
	"Unread messages from %s and %s": "Nieprzeczytane wiadomości od %s i %s",
	"Unread messages from %%s, %%s and one other": {
		"one": "Nieprzeczytane wiadomości od %%s, %%s i jeszcze jednej osoby",
		"other": "Nieprzeczytane wiadomości od %%s, %%s i %d innych osób"
	}
}
<|endoftext|>


<commit_before>/*
 * Line By Line
 *
 * A NodeJS module that helps you reading large text files, line by line,
 * without buffering the files into memory.
 *
 * Copyright (c) 2012 Markus von der Wehd <mvdw@mwin.de>
 * MIT License, see LICENSE.txt, see http://www.opensource.org/licenses/mit-license.php
 */

var path = require('path');
var fs = require('fs');
var events = require("events");

var LineByLineReader = function (filepath, options) {
	var self = this;

	this._filepath = path.normalize(filepath);
	this._encoding = options && options.encoding || 'utf8';
	this._skipEmptyLines = options && options.skipEmptyLines || false;

	this._readStream = null;
	this._lines = [];
	this._lineFragment = '';
	this._paused = false;
	this._end = false;

	events.EventEmitter.call(this);

	process.nextTick(function () {
		self._initStream();
	});
};

LineByLineReader.prototype = Object.create(events.EventEmitter.prototype, {
	constructor: {
		value: LineByLineReader,
		enumerable: false
	}
});

LineByLineReader.prototype._initStream = function () {
	var self = this,
		readStream = fs.createReadStream(this._filepath, { encoding: this._encoding });

	readStream.on('error', function (err) {
		self.emit('error', err);
	});

	readStream.on('data', function (data) {
		self._readStream.pause();
		self._lines = self._lines.concat(data.split(/(?:\n|\r\n|\r)/g));

		self._lines[0] = self._lineFragment + self._lines[0];
		self._lineFragment = self._lines.pop() || '';

		process.nextTick(function () {
			self._nextLine();
		});
	});

	readStream.on('end', function () {
		self._end = true;

		process.nextTick(function () {
			self._nextLine();
		});
	});

	this._readStream = readStream;
};

LineByLineReader.prototype._nextLine = function () {
	var self = this,
		line;

	if (this._end && !!this._lineFragment) {
		this.emit('line', this._lineFragment);
		this._lineFragment = '';

		if (!this._paused) {
			process.nextTick(function () {
				self.emit('end');
			});
		}
		return;
	}

	if (this._end) {
		this.emit('end');
		return;
	}

	if (this._paused) {
		return;
	}

	if (this._lines.length === 0) {
		this._readStream.resume();
		return;
	}

	line = this._lines.shift();

	if (!this._skipEmptyLines || line.length > 0) {
		this.emit('line', line);
	}

	if (!this._paused) {
		process.nextTick(function () {
			self._nextLine();
		});
	}
};

LineByLineReader.prototype.pause = function () {
	this._paused = true;
};

LineByLineReader.prototype.resume = function () {
	var self = this;

	this._paused = false;

	process.nextTick(function () {
		self._nextLine();
	});
};

LineByLineReader.prototype.close = function () {
	var self = this;

	this._readStream.destroy();
	this._end = true;

	process.nextTick(function () {
		self._nextLine();
	});
};

module.exports = LineByLineReader;
<commit_msg>Fixing #1 and #4<commit_after>/*
 * Line By Line
 *
 * A NodeJS module that helps you reading large text files, line by line,
 * without buffering the files into memory.
 *
 * Copyright (c) 2012 Markus von der Wehd <mvdw@mwin.de>
 * MIT License, see LICENSE.txt, see http://www.opensource.org/licenses/mit-license.php
 */

var path = require('path');
var fs = require('fs');
var events = require("events");

// let's make sure we have a setImmediate function (node.js <0.10)
if (typeof setImmediate == 'undefined') { var setImmediate = process.nextTick; }

var LineByLineReader = function (filepath, options) {
	var self = this;

	this._filepath = path.normalize(filepath);
	this._encoding = options && options.encoding || 'utf8';
	this._skipEmptyLines = options && options.skipEmptyLines || false;

	this._readStream = null;
	this._lines = [];
	this._lineFragment = '';
	this._paused = false;
	this._end = false;

	events.EventEmitter.call(this);

	setImmediate(function () {
		self._initStream();
	});
};

LineByLineReader.prototype = Object.create(events.EventEmitter.prototype, {
	constructor: {
		value: LineByLineReader,
		enumerable: false
	}
});

LineByLineReader.prototype._initStream = function () {
	var self = this,
		readStream = fs.createReadStream(this._filepath, { encoding: this._encoding });

	readStream.on('error', function (err) {
		self.emit('error', err);
	});

	readStream.on('data', function (data) {
		self._readStream.pause();
		self._lines = self._lines.concat(data.split(/(?:\n|\r\n|\r)/g));

		self._lines[0] = self._lineFragment + self._lines[0];
		self._lineFragment = self._lines.pop() || '';

		setImmediate(function () {
			self._nextLine();
		});
	});

	readStream.on('end', function () {
		self._end = true;

		setImmediate(function () {
			self._nextLine();
		});
	});

	this._readStream = readStream;
};

LineByLineReader.prototype._nextLine = function () {
	var self = this,
		line;

	if (this._end && !!this._lineFragment) {
		this.emit('line', this._lineFragment);
		this._lineFragment = '';

		if (!this._paused) {
			setImmediate(function () {
				self.emit('end');
			});
		}
		return;
	}
	/*
	 if (this._end) {
	 this.emit('end');
	 return;
	 }
	 */
	if (this._paused) {
		return;
	}

	if (this._lines.length === 0) {
		if (this._end) {
			this.emit('end');
		} else {
			this._readStream.resume();
		}
		return;
	}

	line = this._lines.shift();

	if (!this._skipEmptyLines || line.length > 0) {
		this.emit('line', line);
	}

	if (!this._paused) {
		setImmediate(function () {
			self._nextLine();
		});
	}
};

LineByLineReader.prototype.pause = function () {
	this._paused = true;
};

LineByLineReader.prototype.resume = function () {
	var self = this;

	this._paused = false;

	setImmediate(function () {
		self._nextLine();
	});
};

LineByLineReader.prototype.close = function () {
	var self = this;

	this._readStream.destroy();
	this._end = true;

	setImmediate(function () {
		self._nextLine();
	});
};

module.exports = LineByLineReader;
<|endoftext|>


<commit_before>const _ = require('lodash');
const qs = require('query-string');
const mustache = require('mustache');
const Vibrant = require('node-vibrant');
const stripTags = require('striptags');
const magnetUri = require('magnet-uri');

const messages = require('./messages');

const handleError = err => {
  console.error(JSON.stringify(err.originalError.response.data));
};

const DEFAULT_IMAGE = 'https://yts.am/assets/images/website/logo-YTS.svg';

const MOVIE_DETAIL_TEMPLATE = `{{title_long}}
Casts : {{casts}}
Rating : {{rating}}
Genre: {{genre}}

{{{description_full}}}
`;

const MOVIE_MORE_TEXT = `{{year}}
Duration : {{duration}} minutes
{{mpa_rating}}`;

class Handler {
  constructor(clients) {
    this.lineClient = clients.lineClient;
    this.ytsClient = clients.ytsClient;
    this.firebaseClient = clients.firebaseClient;
    this.osClient = clients.osClient;
    this.serialClient = clients.serialClient;
  }

  handleRequest(payload) {
    payload.events.forEach(event => {
      this.handleEvent(event);
    });
  }

  handleEvent(event) {
    const type = event.type;
    switch (type) {
      case 'message':
        this.handleMessageEvent(event);
        break;

      case 'postback':
        this.handlePostbackEvent(event);
        break;

      case 'follow':
        this.handleFollowEvent(event);
        break;

      default:
        console.log(`Unknown type ${type}`);
        break;
    }
  }

  handleFollowEvent(event) {
    this.lineClient.getProfile(event.source.userId).then(profile => {
      this.firebaseClient.addUser(profile);
      const greetingMessage = `Hello ${
        profile.displayName
      }! My name is YTS Bot. Chat bot to help you search and stream HD movies directly on your chat app.
Try typing "search <your movie title>" to getting started.

Happy watching!`;
      this.lineClient.replyMessage(
        event.replyToken,
        messages.textMessage(greetingMessage)
      );
    });
  }

  handleMessageEvent(event) {
    const type = event.message.type;
    switch (type) {
      case 'text':
        this.handleTextMessage(
          event.replyToken,
          event.source,
          event.message.text
        );
        break;

      default:
        console.log(`unhandled message type ${type}`);
        break;
    }
  }

  sendMovieList(replyToken, result) {
    if (result.movie_count == 0) {
      return this.lineClient
        .replyMessage(
          replyToken,
          messages.textMessage(`Empty result from database`)
        )
        .catch(handleError);
    }

    const carouselMessage = result.movies.map(movie => {
      const actions = [
        messages.actionUriTemplate(
          'Watch Trailer',
          `https://www.youtube.com/watch?v=${movie.yt_trailer_code}`
        ),
        messages.actionPostbackTemplate(
          'Watch Now',
          qs.stringify({
            keyword: 'movie-detail',
            data: movie.id,
          })
        ),
        messages.actionPostbackTemplate(
          'Simillar',
          qs.stringify({
            keyword: 'suggestion',
            data: movie.id,
          })
        ),
      ];
      const backgroundPalettePromise = Vibrant.from(movie.large_cover_image)
        .getPalette()
        .then(palette => {
          return Promise.resolve(palette.Vibrant.getHex());
        })
        .catch(err => {
          return Promise.resolve();
        });
      return backgroundPalettePromise.then(background => {
        return messages.carouselColumnTemplate(
          movie.large_cover_image,
          _.truncate(movie.title, { length: 35, separator: /\W/ }),
          _.truncate(movie.summary, { length: 50, separator: /\W/ }),
          actions,
          background
        );
      });
    });

    return Promise.all(carouselMessage).then(carouselColumns => {
      console.log(JSON.stringify(carouselColumns));
      const message = messages.templateMessage(
        `Search result`,
        messages.carouselTemplate(carouselColumns, 'rectangle', 'contain')
      );
      return this.lineClient
        .replyMessage(replyToken, message)
        .catch(handleError);
    });
  }

  handleTextMessage(replyToken, source, text) {
    const token = text.split(' ');
    const keyword = token[0].toLowerCase();
    const term = token.slice(1, token.length).join(' ');
    switch (keyword) {
      case 'search':
        this.ytsClient
          .searchMovie(term)
          .then(result => {
            return this.sendMovieList(replyToken, result);
          })
          .catch(handleError);
        break;
      case 'link':
        this.processTorrent(replyToken, source, {
          movie: term,
          image: '',
          title: term,
          qty: '',
          custom: true,
        });
        break;
      case 'series':
        this.serialClient.searchSeries(term).then(result => {
          return this.sendSeriesList(replyToken, result);
        });
        break;
      default:
        console.log(`unknown keyword ${keyword}`);
        break;
    }
  }

  sendSeriesList(replyToken, result) {
    const seriesList = _.slice(
      _.orderBy(result.serials, ['imdb_rating'], ['desc']),
      0,
      10
    ).map(serial => {
      const actionDetails = messages.actionPostbackTemplate(
        'Details',
        qs.stringify({ keyword: 'series-detail', id: serial.id })
      );
      const actionSubscribe = messages.actionPostbackTemplate(
        'Subscribe',
        qs.stringify({ keyword: 'series-subscribe', id: serial.id })
      );
      const actions = [actionDetails, actionSubscribe];
      const schedule =
        serial.airday && serial.airtime
          ? `every ${serial.airday} on ${serial.airtime}`
          : 'Ended';

      const mainText = `${serial.start} - ${serial.end}\n${schedule}`;

      const mainImage = serial.poster_id
        ? this.serialClient.getImageUrl(serial.poster.name)
        : DEFAULT_IMAGE;

      const backgroundPalettePromise = Vibrant.from(mainImage)
        .getPalette()
        .then(palette => {
          return Promise.resolve(palette.Vibrant.getHex());
        })
        .catch(err => {
          return Promise.resolve();
        });
      return backgroundPalettePromise.then(background => {
        return messages.carouselColumnTemplate(
          mainImage,
          _.truncate(serial.title, { length: 35, separator: /\W/ }),
          _.truncate(mainText, { length: 50, separator: /\W/ }),
          actions,
          background
        );
      });
    });

    return Promise.all(seriesList).then(carouselColumns => {
      console.log(JSON.stringify(carouselColumns));
      const message = messages.templateMessage(
        `Search result`,
        messages.carouselTemplate(carouselColumns, 'rectangle', 'contain')
      );
      return this.lineClient
        .replyMessage(replyToken, message)
        .catch(handleError);
    });
  }

  handlePostbackEvent(event) {
    const postbackData = event.postback.data;
    this.handlePostback(event.replyToken, event.source, postbackData);
  }

  processTorrent(replyToken, source, params) {
    console.log(params);
    const contentString = params.custom
      ? 'your torrent'
      : `${params.title} (${params.qty})`;
    this.firebaseClient.addWatchSession(source.userId, params).then(result => {
      const unsubscribe = this.firebaseClient
        .getFirestore()
        .doc(`/session/${result.id}`)
        .onSnapshot(doc => {
          if (doc.data().status === 'ready') {
            const sessionData = doc.data();
            unsubscribe();
            let osParams = {
              episode: params.episode,
              season: params.season,
              filename: params.filename
            };
            if (params.custom) {
              osParams = Object.assign(osParams, {
                filename: doc.data().title,
              });
            } else {
              osParams = Object.assign(osParams, {
                imdbid: params.imdb,
                filesize: params.size,
              });
            }
            this.osClient.getSubsLink(result.id, osParams).then(() => {
              this.lineClient.pushMessage(
                sessionData.userId,
                messages.textMessage(
                  `Watch ${contentString} here\n${result.url}`
                )
              );
            });
          }
        });
    });
    this.lineClient.replyMessage(
      replyToken,
      messages.textMessage(
        `Preparing ${contentString}...\nWe will notify you once the movie is ready`
      )
    );
  }

  handlePostback(replyToken, source, data) {
    const parsedData = qs.parse(data);
    switch (parsedData.keyword) {
      case 'movie-detail':
        this.ytsClient
          .getMovie(parsedData.data)
          .then(result => {
            const movie = result.movie;
            const movieImageMessage = messages.imageMessage(
              movie.large_cover_image,
              movie.medium_cover_image
            );
            const movieDetailTextMessage = messages.textMessage(
              mustache.render(MOVIE_DETAIL_TEMPLATE, {
                title_long: movie.title_long,
                casts: (movie.cast || []).map(cast => cast.name).join(', '),
                rating: movie.rating,
                description_full: movie.description_full,
                genre: (movie.genres || []).join(', '),
              })
            );
            const watchActions = movie.torrents.slice(0, 3).map(torr => {
              return messages.actionPostbackTemplate(
                `Watch in ${torr.quality}`,
                qs.stringify({
                  keyword: 'watchlink',
                  movieId: movie.id,
                  movie: torr.url,
                  qty: torr.quality,
                  size: torr.size_bytes,
                })
              );
            });

            const buttonTemplate = messages.buttonTemplate(
              undefined,
              movie.title,
              mustache.render(MOVIE_MORE_TEXT, {
                year: movie.year,
                duration: movie.runtime,
                mpa_rating: movie.mpa_rating,
              }),
              watchActions
            );

            const buttonMessage = messages.templateMessage(
              `${movie.title} options`,
              buttonTemplate
            );
            this.lineClient
              .replyMessage(replyToken, [
                movieImageMessage,
                movieDetailTextMessage,
                buttonMessage,
              ])
              .catch(handleError);
          })
          .catch(handleError);
        break;

      case 'suggestion':
        this.ytsClient.getSuggestions(parsedData.data).then(result => {
          return this.sendMovieList(replyToken, result);
        });
        break;

      case 'watchlink':
        this.ytsClient.getMovie(parsedData.movieId).then(result => {
          const movie = result.movie;
          const movieData = {
            movie: parsedData.movie,
            image: movie.large_cover_image,
            title: movie.title,
            qty: parsedData.qty,
            imdb: movie.imdb_code,
            size: parsedData.size,
          };
          this.processTorrent(replyToken, source, movieData);
        });
        break;
      case 'series-detail':
        this.serialClient
          .seriesDetails(parsedData.id)
          .then(result => this.sendSeriesDetails(replyToken, result.serial));
        break;
      case 'series-watch-link':
        this.serialClient.seriesDetails(parsedData.id).then(result => {
          const series = result.serial;
          const movieData = {
            movie: parsedData.url,
            size: parsedData.size,
            title: `${series.title} S${parsedData.season}·E${parsedData.ep}`,
            season: parsedData.season,
            episode: parsedData.ep,
            image: series.poster_id
              ? this.serialClient.getImageUrl(series.poster.name)
              : DEFAULT_IMAGE,
              qty: parsedData.qty
          };
          this.processTorrent(replyToken, source, movieData);
        });
        break;
      default:
        break;
    }
  }

  sendSeriesDetails(replyToken, series) {
    const seriesDetailMessages = [];
    if (series.poster_id) {
      seriesDetailMessages.push(
        messages.imageMessage(
          this.serialClient.getImageUrl(series.poster.name),
          this.serialClient.getImageUrl(series.poster.name)
        )
      );
    }
    const textDetails = [];
    if (series.genre) {
      const allGenre = series.genre.map(genre => genre.name).join(', ');
      textDetails.push(`Genre : ${allGenre}`);
    }
    if (series.network) {
      const allNetwork = series.network.map(network => network.name).join(', ');
      textDetails.push(`Network : ${allNetwork}`);
    }
    if (series.description.length > 0) {
      textDetails.push('', stripTags(series.description[0].body));
    }
    seriesDetailMessages.push(messages.textMessage(textDetails.join('\n')));
    const latestEpisode = [];
    let episodeIndex = series.ep.length - 1;
    while (latestEpisode.length < 10) {
      const currentEpi = series.ep[episodeIndex];
      if (currentEpi.torrent && currentEpi.torrent.length > 0) {
        const quality = {};
        _.orderBy(currentEpi.torrent, ['seed']).forEach(torr => {
          if (!quality[torr.quality.name]) {
            quality[torr.quality.name] = { url: torr.value, size: torr.size };
          }
        });
        const actions = _.slice(Object.keys(quality), 0, 3).map(qty => {
          const parsedMagnet = magnetUri.decode(quality[qty].url);
          const trimmedUrlObject = Object.assign(parsedMagnet, {
            announce: [],
            tr: [],
          });
          return messages.actionPostbackTemplate(
            `Watch in ${qty}`,
            qs.stringify({
              keyword: 'series-watch-link',
              id: series.id,
              url: magnetUri.encode(trimmedUrlObject),
              size: quality[qty].size,
              season: currentEpi.season,
              ep: currentEpi.ep,
              qty
            })
          );
        });
        const episodeButton = messages.carouselColumnTemplate(
          undefined,
          `S${currentEpi.season}·E${currentEpi.ep}`,
          currentEpi.title,
          actions
        );
        latestEpisode.push(episodeButton);
      }
      episodeIndex--;
    }
    const episodeCarrousel = messages.templateMessage(
      `Search result`,
      messages.carouselTemplate(latestEpisode, 'rectangle', 'contain')
    );
    seriesDetailMessages.push(episodeCarrousel);
    this.lineClient
      .replyMessage(replyToken, seriesDetailMessages)
      .catch(handleError);
  }
}

module.exports = Handler;
<commit_msg>add imdb for subtitles<commit_after>const _ = require('lodash');
const qs = require('query-string');
const mustache = require('mustache');
const Vibrant = require('node-vibrant');
const stripTags = require('striptags');
const magnetUri = require('magnet-uri');

const messages = require('./messages');

const handleError = err => {
  console.error(JSON.stringify(err.originalError.response.data));
};

const DEFAULT_IMAGE = 'https://yts.am/assets/images/website/logo-YTS.svg';

const MOVIE_DETAIL_TEMPLATE = `{{title_long}}
Casts : {{casts}}
Rating : {{rating}}
Genre: {{genre}}

{{{description_full}}}
`;

const MOVIE_MORE_TEXT = `{{year}}
Duration : {{duration}} minutes
{{mpa_rating}}`;

class Handler {
  constructor(clients) {
    this.lineClient = clients.lineClient;
    this.ytsClient = clients.ytsClient;
    this.firebaseClient = clients.firebaseClient;
    this.osClient = clients.osClient;
    this.serialClient = clients.serialClient;
  }

  handleRequest(payload) {
    payload.events.forEach(event => {
      this.handleEvent(event);
    });
  }

  handleEvent(event) {
    const type = event.type;
    switch (type) {
      case 'message':
        this.handleMessageEvent(event);
        break;

      case 'postback':
        this.handlePostbackEvent(event);
        break;

      case 'follow':
        this.handleFollowEvent(event);
        break;

      default:
        console.log(`Unknown type ${type}`);
        break;
    }
  }

  handleFollowEvent(event) {
    this.lineClient.getProfile(event.source.userId).then(profile => {
      this.firebaseClient.addUser(profile);
      const greetingMessage = `Hello ${
        profile.displayName
      }! My name is YTS Bot. Chat bot to help you search and stream HD movies directly on your chat app.
Try typing "search <your movie title>" to getting started.

Happy watching!`;
      this.lineClient.replyMessage(
        event.replyToken,
        messages.textMessage(greetingMessage)
      );
    });
  }

  handleMessageEvent(event) {
    const type = event.message.type;
    switch (type) {
      case 'text':
        this.handleTextMessage(
          event.replyToken,
          event.source,
          event.message.text
        );
        break;

      default:
        console.log(`unhandled message type ${type}`);
        break;
    }
  }

  sendMovieList(replyToken, result) {
    if (result.movie_count == 0) {
      return this.lineClient
        .replyMessage(
          replyToken,
          messages.textMessage(`Empty result from database`)
        )
        .catch(handleError);
    }

    const carouselMessage = result.movies.map(movie => {
      const actions = [
        messages.actionUriTemplate(
          'Watch Trailer',
          `https://www.youtube.com/watch?v=${movie.yt_trailer_code}`
        ),
        messages.actionPostbackTemplate(
          'Watch Now',
          qs.stringify({
            keyword: 'movie-detail',
            data: movie.id,
          })
        ),
        messages.actionPostbackTemplate(
          'Simillar',
          qs.stringify({
            keyword: 'suggestion',
            data: movie.id,
          })
        ),
      ];
      const backgroundPalettePromise = Vibrant.from(movie.large_cover_image)
        .getPalette()
        .then(palette => {
          return Promise.resolve(palette.Vibrant.getHex());
        })
        .catch(err => {
          return Promise.resolve();
        });
      return backgroundPalettePromise.then(background => {
        return messages.carouselColumnTemplate(
          movie.large_cover_image,
          _.truncate(movie.title, { length: 35, separator: /\W/ }),
          _.truncate(movie.summary, { length: 50, separator: /\W/ }),
          actions,
          background
        );
      });
    });

    return Promise.all(carouselMessage).then(carouselColumns => {
      console.log(JSON.stringify(carouselColumns));
      const message = messages.templateMessage(
        `Search result`,
        messages.carouselTemplate(carouselColumns, 'rectangle', 'contain')
      );
      return this.lineClient
        .replyMessage(replyToken, message)
        .catch(handleError);
    });
  }

  handleTextMessage(replyToken, source, text) {
    const token = text.split(' ');
    const keyword = token[0].toLowerCase();
    const term = token.slice(1, token.length).join(' ');
    switch (keyword) {
      case 'search':
        this.ytsClient
          .searchMovie(term)
          .then(result => {
            return this.sendMovieList(replyToken, result);
          })
          .catch(handleError);
        break;
      case 'link':
        this.processTorrent(replyToken, source, {
          movie: term,
          image: '',
          title: term,
          qty: '',
          custom: true,
        });
        break;
      case 'series':
        this.serialClient.searchSeries(term).then(result => {
          return this.sendSeriesList(replyToken, result);
        });
        break;
      default:
        console.log(`unknown keyword ${keyword}`);
        break;
    }
  }

  sendSeriesList(replyToken, result) {
    const seriesList = _.slice(
      _.orderBy(result.serials, ['imdb_rating'], ['desc']),
      0,
      10
    ).map(serial => {
      const actionDetails = messages.actionPostbackTemplate(
        'Details',
        qs.stringify({ keyword: 'series-detail', id: serial.id })
      );
      const actionSubscribe = messages.actionPostbackTemplate(
        'Subscribe',
        qs.stringify({ keyword: 'series-subscribe', id: serial.id })
      );
      const actions = [actionDetails, actionSubscribe];
      const schedule =
        serial.airday && serial.airtime
          ? `every ${serial.airday} on ${serial.airtime}`
          : 'Ended';

      const mainText = `${serial.start} - ${serial.end}\n${schedule}`;

      const mainImage = serial.poster_id
        ? this.serialClient.getImageUrl(serial.poster.name)
        : DEFAULT_IMAGE;

      const backgroundPalettePromise = Vibrant.from(mainImage)
        .getPalette()
        .then(palette => {
          return Promise.resolve(palette.Vibrant.getHex());
        })
        .catch(err => {
          return Promise.resolve();
        });
      return backgroundPalettePromise.then(background => {
        return messages.carouselColumnTemplate(
          mainImage,
          _.truncate(serial.title, { length: 35, separator: /\W/ }),
          _.truncate(mainText, { length: 50, separator: /\W/ }),
          actions,
          background
        );
      });
    });

    return Promise.all(seriesList).then(carouselColumns => {
      console.log(JSON.stringify(carouselColumns));
      const message = messages.templateMessage(
        `Search result`,
        messages.carouselTemplate(carouselColumns, 'rectangle', 'contain')
      );
      return this.lineClient
        .replyMessage(replyToken, message)
        .catch(handleError);
    });
  }

  handlePostbackEvent(event) {
    const postbackData = event.postback.data;
    this.handlePostback(event.replyToken, event.source, postbackData);
  }

  processTorrent(replyToken, source, params) {
    console.log(params);
    const contentString = params.custom
      ? 'your torrent'
      : `${params.title} (${params.qty})`;
    this.firebaseClient.addWatchSession(source.userId, params).then(result => {
      const unsubscribe = this.firebaseClient
        .getFirestore()
        .doc(`/session/${result.id}`)
        .onSnapshot(doc => {
          if (doc.data().status === 'ready') {
            const sessionData = doc.data();
            unsubscribe();
            let osParams = {
              episode: params.episode,
              season: params.season,
              filename: params.filename
            };
            if (params.custom) {
              osParams = Object.assign(osParams, {
                filename: doc.data().title,
              });
            } else {
              osParams = Object.assign(osParams, {
                imdbid: params.imdb,
                filesize: params.size,
              });
            }
            this.osClient.getSubsLink(result.id, osParams).then(() => {
              this.lineClient.pushMessage(
                sessionData.userId,
                messages.textMessage(
                  `Watch ${contentString} here\n${result.url}`
                )
              );
            });
          }
        });
    });
    this.lineClient.replyMessage(
      replyToken,
      messages.textMessage(
        `Preparing ${contentString}...\nWe will notify you once the movie is ready`
      )
    );
  }

  handlePostback(replyToken, source, data) {
    const parsedData = qs.parse(data);
    switch (parsedData.keyword) {
      case 'movie-detail':
        this.ytsClient
          .getMovie(parsedData.data)
          .then(result => {
            const movie = result.movie;
            const movieImageMessage = messages.imageMessage(
              movie.large_cover_image,
              movie.medium_cover_image
            );
            const movieDetailTextMessage = messages.textMessage(
              mustache.render(MOVIE_DETAIL_TEMPLATE, {
                title_long: movie.title_long,
                casts: (movie.cast || []).map(cast => cast.name).join(', '),
                rating: movie.rating,
                description_full: movie.description_full,
                genre: (movie.genres || []).join(', '),
              })
            );
            const watchActions = movie.torrents.slice(0, 3).map(torr => {
              return messages.actionPostbackTemplate(
                `Watch in ${torr.quality}`,
                qs.stringify({
                  keyword: 'watchlink',
                  movieId: movie.id,
                  movie: torr.url,
                  qty: torr.quality,
                  size: torr.size_bytes,
                })
              );
            });

            const buttonTemplate = messages.buttonTemplate(
              undefined,
              movie.title,
              mustache.render(MOVIE_MORE_TEXT, {
                year: movie.year,
                duration: movie.runtime,
                mpa_rating: movie.mpa_rating,
              }),
              watchActions
            );

            const buttonMessage = messages.templateMessage(
              `${movie.title} options`,
              buttonTemplate
            );
            this.lineClient
              .replyMessage(replyToken, [
                movieImageMessage,
                movieDetailTextMessage,
                buttonMessage,
              ])
              .catch(handleError);
          })
          .catch(handleError);
        break;

      case 'suggestion':
        this.ytsClient.getSuggestions(parsedData.data).then(result => {
          return this.sendMovieList(replyToken, result);
        });
        break;

      case 'watchlink':
        this.ytsClient.getMovie(parsedData.movieId).then(result => {
          const movie = result.movie;
          const movieData = {
            movie: parsedData.movie,
            image: movie.large_cover_image,
            title: movie.title,
            qty: parsedData.qty,
            imdb: movie.imdb_code,
            size: parsedData.size,
          };
          this.processTorrent(replyToken, source, movieData);
        });
        break;
      case 'series-detail':
        this.serialClient
          .seriesDetails(parsedData.id)
          .then(result => this.sendSeriesDetails(replyToken, result.serial));
        break;
      case 'series-watch-link':
        this.serialClient.seriesDetails(parsedData.id).then(result => {
          const series = result.serial;
          const movieData = {
            movie: parsedData.url,
            size: parsedData.size,
            title: `${series.title} S${parsedData.season}·E${parsedData.ep}`,
            season: parsedData.season,
            episode: parsedData.ep,
            imdb: series.imdb_id.split('/').pop(),
            image: series.poster_id
              ? this.serialClient.getImageUrl(series.poster.name)
              : DEFAULT_IMAGE,
              qty: parsedData.qty
          };
          this.processTorrent(replyToken, source, movieData);
        });
        break;
      default:
        break;
    }
  }

  sendSeriesDetails(replyToken, series) {
    const seriesDetailMessages = [];
    if (series.poster_id) {
      seriesDetailMessages.push(
        messages.imageMessage(
          this.serialClient.getImageUrl(series.poster.name),
          this.serialClient.getImageUrl(series.poster.name)
        )
      );
    }
    const textDetails = [];
    if (series.genre) {
      const allGenre = series.genre.map(genre => genre.name).join(', ');
      textDetails.push(`Genre : ${allGenre}`);
    }
    if (series.network) {
      const allNetwork = series.network.map(network => network.name).join(', ');
      textDetails.push(`Network : ${allNetwork}`);
    }
    if (series.description.length > 0) {
      textDetails.push('', stripTags(series.description[0].body));
    }
    seriesDetailMessages.push(messages.textMessage(textDetails.join('\n')));
    const latestEpisode = [];
    let episodeIndex = series.ep.length - 1;
    while (latestEpisode.length < 10) {
      const currentEpi = series.ep[episodeIndex];
      if (currentEpi.torrent && currentEpi.torrent.length > 0) {
        const quality = {};
        _.orderBy(currentEpi.torrent, ['seed']).forEach(torr => {
          if (!quality[torr.quality.name]) {
            quality[torr.quality.name] = { url: torr.value, size: torr.size };
          }
        });
        const actions = _.slice(Object.keys(quality), 0, 3).map(qty => {
          const parsedMagnet = magnetUri.decode(quality[qty].url);
          const trimmedUrlObject = Object.assign(parsedMagnet, {
            announce: [],
            tr: [],
          });
          return messages.actionPostbackTemplate(
            `Watch in ${qty}`,
            qs.stringify({
              keyword: 'series-watch-link',
              id: series.id,
              url: magnetUri.encode(trimmedUrlObject),
              size: quality[qty].size,
              season: currentEpi.season,
              ep: currentEpi.ep,
              qty
            })
          );
        });
        const episodeButton = messages.carouselColumnTemplate(
          undefined,
          `S${currentEpi.season}·E${currentEpi.ep}`,
          currentEpi.title,
          actions
        );
        latestEpisode.push(episodeButton);
      }
      episodeIndex--;
    }
    const episodeCarrousel = messages.templateMessage(
      `Search result`,
      messages.carouselTemplate(latestEpisode, 'rectangle', 'contain')
    );
    seriesDetailMessages.push(episodeCarrousel);
    this.lineClient
      .replyMessage(replyToken, seriesDetailMessages)
      .catch(handleError);
  }
}

module.exports = Handler;
<|endoftext|>


<commit_before>const s3 = require("s3");

// Shamelessly copied from s3 README
// This is here because the Circle CI s3 orb takes 20s
// to install awscli python package on every build

const s3Options = {
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: process.env.AWS_REGION,
};

const client = s3.createClient({ s3Options });

const params = {
  localDir: "build",
  deleteRemoved: true,

  s3Params: {
    Bucket: "ted.pennin.gs",
    ACL: "public-read",
  },
};

const uploader = client.uploadDir(params);

uploader.on("error", function (err) {
  console.error("unable to sync:", err.stack);
});
uploader.on("progress", function () {
  console.log("progress", uploader.progressAmount, uploader.progressTotal);
});
uploader.on("end", function () {
  console.log("done uploading");
});
<commit_msg>Add a cache control header (#31)<commit_after>const s3 = require("s3");

// Shamelessly copied from s3 README
// This is here because the Circle CI s3 orb takes 20s
// to install awscli python package on every build

const s3Options = {
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: process.env.AWS_REGION,
};

const client = s3.createClient({ s3Options });

const params = {
  localDir: "build",
  deleteRemoved: true,

  s3Params: {
    Bucket: "ted.pennin.gs",
    ACL: "public-read",
    CacheControl: "no-cache",
  },
};

const uploader = client.uploadDir(params);

uploader.on("error", function (err) {
  console.error("unable to sync:", err.stack);
});
uploader.on("progress", function () {
  console.log("progress", uploader.progressAmount, uploader.progressTotal);
});
uploader.on("end", function () {
  console.log("done uploading");
});
<|endoftext|>


<commit_before>'use strict'

// This should be called once per download
// Each event has a dataChannel that the data will be piped throught
self.onmessage = event => {
    // Create a uniq link for the download
    let uniqLink = 'intercept-me-nr' + Math.random()

    let p = new Promise((resolve, reject) => {
        let stream = createStream(resolve, reject, event.ports[0])
        hijacke(uniqLink, stream, event.data, event.ports[0])
    })

    // Beginning in Chrome 51, event is an ExtendableMessageEvent, which supports
    // the waitUntil() method for extending the lifetime of the event handler
    // until the promise is resolved.
    if ('waitUntil' in event) {
        event.waitUntil(p)
    }

    // Without support for waitUntil(), there's a chance that if the promise chain
    // takes "too long" to execute, the service worker might be automatically
    // stopped before it's complete.
}

function createStream(resolve, reject, port){
    // ReadableStream is only supported by chrome 52, but can be enabled
    // with a flag chrome://flags/#enable-experimental-web-platform-features
    var bytesWritten = 0
    return new ReadableStream({
		start(controller) {
			port.postMessage({debug: 'ReadableStream has been created'})
			// When we recive data on the messageChannel, we write
			port.onmessage = event => {
				// We finaly have a abortable stream =D
				if(event.data === 'end'){
                    resolve()
                    return controller.close()
                }
                controller.enqueue(event.data)
                bytesWritten += event.data.byteLength
                port.postMessage({ bytesWritten })
			}
		},
		cancel() {
			console.log("user aborted")
		}
	})
}

function hijacke(uniqLink, stream, data, port){
	let listener, filename, headers

	if(typeof data === 'string')
		filename = data

	headers = {
		'Content-Type': 'application/octet-stream; charset=utf-8',
		'Content-Disposition': 'attachment; filename=' + (filename || data.filename)
	}

	if(data.size)
		headers['Content-Length'] = data.size

    self.addEventListener('fetch', listener = event => {
		console.log("Handleing ", event.request.url)
        if(!event.request.url.includes(uniqLink))
    		return

        port.postMessage({debug: 'Mocking a download request'})

        self.removeEventListener('fetch', listener)

    	let res = new Response(stream, { headers })

    	event.respondWith(res)
    })

	// Any better way to get the scope of the service worker???
	port.postMessage({download: location.href.replace('sw.js', uniqLink)})
}
<commit_msg>A better way to get the scope of the service worker<commit_after>'use strict'

// This should be called once per download
// Each event has a dataChannel that the data will be piped throught
self.onmessage = event => {
    // Create a uniq link for the download
    let uniqLink = 'intercept-me-nr' + Math.random()

    let p = new Promise((resolve, reject) => {
        let stream = createStream(resolve, reject, event.ports[0])
        hijacke(uniqLink, stream, event.data, event.ports[0])
    })

    // Beginning in Chrome 51, event is an ExtendableMessageEvent, which supports
    // the waitUntil() method for extending the lifetime of the event handler
    // until the promise is resolved.
    if ('waitUntil' in event) {
        event.waitUntil(p)
    }

    // Without support for waitUntil(), there's a chance that if the promise chain
    // takes "too long" to execute, the service worker might be automatically
    // stopped before it's complete.
}

function createStream(resolve, reject, port){
    // ReadableStream is only supported by chrome 52, but can be enabled
    // with a flag chrome://flags/#enable-experimental-web-platform-features
    var bytesWritten = 0
    return new ReadableStream({
		start(controller) {
			port.postMessage({debug: 'ReadableStream has been created'})
			// When we recive data on the messageChannel, we write
			port.onmessage = event => {
				// We finaly have a abortable stream =D
				if(event.data === 'end'){
                    resolve()
                    return controller.close()
                }
                controller.enqueue(event.data)
                bytesWritten += event.data.byteLength
                port.postMessage({ bytesWritten })
			}
		},
		cancel() {
			console.log("user aborted")
		}
	})
}

function hijacke(uniqLink, stream, data, port){
	let listener, filename, headers

	if(typeof data === 'string')
		filename = data

	headers = {
		'Content-Type': 'application/octet-stream; charset=utf-8',
		'Content-Disposition': 'attachment; filename=' + (filename || data.filename)
	}

	if(data.size)
		headers['Content-Length'] = data.size

    self.addEventListener('fetch', listener = event => {
		console.log("Handleing ", event.request.url)
        if(!event.request.url.includes(uniqLink))
    		return

        port.postMessage({debug: 'Mocking a download request'})

        self.removeEventListener('fetch', listener)

    	let res = new Response(stream, { headers })

    	event.respondWith(res)
    })

	port.postMessage({download: self.registration.scope + uniqLink})
}
<|endoftext|>


<commit_before>self.addEventListener('install', function(event) {
  event.waitUntil(
      caches.open('dm').then(function(cache) {
        return cache.addAll([
            '/',
            '/index.html',
            '/public/css/styles.css',
            '/public/js/main.js'
          ]);
      })
    );
});

self.addEventListener('fetch', function(event) {
  console.log(event.request.url);
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});
<commit_msg>Remove request url logging<commit_after>self.addEventListener('install', function(event) {
  event.waitUntil(
      caches.open('dm').then(function(cache) {
        return cache.addAll([
            '/',
            '/index.html',
            '/public/css/styles.css',
            '/public/js/main.js'
          ]);
      })
    );
});

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});
<|endoftext|>