<commit_before>/**
 * 弹出层
 */
onkeydown(document, function (e) {
    if (e.which === 27 && rootElements.length) {
        var r = rootElements.pop();
        r.blur();
        remove(r);
    }
});
var animationStyle = "opacity:0;transform:scale(1.2);transition:.1s opacity ease-out,.2s transform ease-out;";
var setInitialStyle = function (element) {
    if (!element.initialStyle) element.initialStyle = animationStyle;
};
var windowFactory = function (window) {
    css(window, `min-height:10px;min-width:10px;position:absolute;`);
    return window;
};
var loadingFactory = function (element) {
    css(element, `position:absolute;`);
    return element;
}
/**
 * 弹出子窗口的函数
 * @param {string} path 窗口路径的唯一索引
 * @param {any} parameters 给窗口的作域预先赋值
 * @param {object} style 自定义样式，主要是width,height等位置参数
 * @returns {Element} 给调用弹窗的命令暴露的可选方法
 */
var popup = function (path) {
    if (isString(path)) {
        return popup_path.apply(null, arguments);
    }
    if (isNode(path)) {
        return popup_view.apply(null, arguments);
    }
    throw new Error(`path isn't valid:${path}`);
};
var popup_path = function (path = "", parameters, target) {
    if (!popup.go || !popup.prepare) throw new Error("当前环境无法使用");
    // 3 has mask has view control
    var element;
    if (/^[#\+]/.test(path)) {
        // mask
        path = path.replace(/^[#\+]/, "");
        var load = function () {
            windowFactory(element);
            setInitialStyle(element);
            popup_with_mask(element, target);
            if (element.dragHandle) drag.on(element);
        };
    }
    // 2 has view control has no mask
    else if (/^@/.test(path)) {
        path = path.replace(/^@/, "");
        var load = function () {
            windowFactory(element);
            setInitialStyle(element);
            popup_as_single(element);
            if (element.dragHandle) drag.on(element);
        };
    }
    // 1 has mask has no control
    else if (/^!/.test(path)) {
        path = path.replace(/^!/, "");
        var load = function () {
            loadingFactory(element);
            setInitialStyle(element);
            popup_with_mask(element);
        };
    }
    // 0 has no mask no control
    else {
        var load = function () {
            loadingFactory(element);
            if (isNode(target)) {
                popup_as_extra(element, target);
            }
            else if (target instanceof Event) {
                popup_to_event(element, target);
            } else if (target instanceof Array) {
                popup_to_point(element, target);
            } else if (target) {
                popup_with_mask(element);
            } else {
                if (parameters !== false && target !== false) setInitialStyle(element);
                popup_as_single(element);
            }
        };
    }
    var fullfill = function () {
        remove(element);
        remove(element && element.mask);
        element = popup.create(path, parameters);
        if (!element) return;
        load();
        element.$reload = fullfill;
        if (!target && target !== false && parameters !== false) {
            element.style.opacity = 0;
            if (element.parentNode) move.bindPosition(element, [.5, .5]), element.style.opacity = 1;
            else once('append')(element, function () {
                move.bindPosition(element, [.5, .5]);
                element.style.opacity = 1;
            });
        }
    };
    popup.prepare(path, fullfill);
    return element;
};

var popup_view = function (element, target, style) {
    if (isNode(target)) {
        if (target.isMask) {
            popup_with_mask(element, target);
            return element;
        }
        return popup_as_extra(element, target, style);
    }
    if (target instanceof Event) {
        popup_to_event(element, target);
    } else if (target instanceof Array) {
        popup_to_point(element, target);
    } else if (target) {
        popup_with_mask(element);
    } else {
        popup_as_single(element);
    }
    return element;
};

css(".mask", `position:absolute;position:fixed;left:0;right:0;bottom:0;top:0;width:auto;height:auto;background:#000;opacity:0.2;transform:scale(1);display:block`);
var createMask = function (element) {
    var masks = element.with;
    if (masks) for (var cx = 0, dx = masks.length; cx < dx; cx++) {
        var m = masks[cx];
        if (m.isMask) return m;
    }
    var mask = document.createElement("mask");
    mask.initialStyle = animationStyle;
    css(mask, `z-index:${zIndex()};`);
    mask.className = "mask";
    mask.isMask = true;
    return mask;
};
var popup_with_mask = function (element, mask = createMask(element)) {
    if (!mask.clean) {
        mask.clean = new Cleanup(element.with);
    }
    onremove(element, mask.clean);
    css(element, `z-index:${zIndex()};`);
    if (mask.parentNode) {
        global(element, false);
    }
    if (!element.with) {
        element.with = [mask];
    } else if (element.with instanceof Array) {
        if (!~element.with.indexOf(element)) element.with.push(mask);
    } else {
        if (element.with !== element) element.with = [element.with, mask];
    }
    if (!element.parentNode) global(element, false);
    return element;
};
var isypop = function (target) {
    var { offsetParent, nextSibling, previousSibling } = target;
    if (
        nextSibling
        && (
            nextSibling.offsetLeft - target.offsetLeft >= target.offsetWidth / 2
            || target.offsetLeft - nextSibling.offsetLeft >= nextSibling.offsetWidth / 2
        )
        || previousSibling
        && (
            previousSibling.offsetLeft - target.offsetLeft >= target.offsetWidth / 2
            || target.offsetLeft - previousSibling.offsetLeft >= previousSibling.offsetWidth / 2
        )
    ) return true;
    if (offsetParent && target.offsetTop / target.offsetHeight < .2 && offsetParent.offsetWidth / target.offsetWidth > 1.5) return true;

};
var isxpop = arriswise(isypop, arguments);
var popup_as_extra = function (element, target, style) {
    if (style) {
        if (/^[vy]/i.test(style)) {
            popup_as_yextra(element, target, style);
            return;
        }
        if (/^[hx]/i.test(style)) {
            popup_as_xextra(element, target, style);
            return;
        }

    }
    if (isypop(target)) {
        popup_as_yextra(element, target, style);
    } else if (isxpop(target)) {
        popup_as_xextra(element, target, style);
    } else if (isypop(target.parentNode)) {
        popup_as_yextra(element, target, style);
    } else if (isxpop(target.parentNode)) {
        popup_as_xextra(element, target, style);
    } else {
        if (/inline|cell/i.test(getComputedStyle(target).display)) {
            popup_as_yextra(element, target, style);
        } else {
            popup_as_xextra(element, target, style);
        }
    }

};
var _as_yextra = function (global, innerWidth, innerHeight, element, target, pointer) {
    var withRhomb = pointer && /rhomb|rhombus|diamond|lozenge|triangle|[drlt]$/i.test(pointer);
    if (withRhomb) {
        var _rhomb = rhomb();
    }
    if (!element.origin) {
        element.origin = {
            height: element.style.height,
            width: element.style.width,
            minWidth: element.style.minWidth,
            display: element.style.display
        };
    }
    var zindex = zIndex();
    css(element, `position:absolute;z-index:${zindex}`);
    css(_rhomb, { zIndex: zindex });
    var release1 = onremove(target, function () {
        remove(element);
    });
    var release2 = onremove(element, function () {
        release1();
        release2();
        remove(_rhomb);
        if (document.removeEventListener) {
            document.removeEventListener("scroll", reshape, true);
            window.removeEventListener("resize", reshape);
        }
    });
    global(element, false);
    var reshape = function () {
        extend(element.style, element.origin);
        var position = getScreenPosition(target);
        var viewrect = getScreenPosition(element.offsetParent);
        var documentbody = document.body;
        if (element.offsetParent === documentbody && !/^(absolute|relative|fixed)$/i.test(getComputedStyle(documentbody).position)) {
            viewrect.left = 0;
            viewrect.top = 0;
            viewrect.right += innerWidth - viewrect.right;
            viewrect.bottom += innerHeight - viewrect.bottom;
            viewrect.width = viewrect.right - viewrect.left;
            viewrect.height = viewrect.bottom - viewrect.top;
        } else {
            viewrect.top += documentbody.clientTop;
            viewrect.left += documentbody.clientLeft;
            viewrect.height = documentbody.clientHeight;
            viewrect.width = documentbody.clientWidth;
            viewrect.right = viewrect.left + viewrect.width;
            viewrect.bottom = viewrect.top + viewrect.height;
        }
        var maxHeight = Math.max(position.top, innerHeight - position.top - position.height);
        var maxWidth = Math.max(position.left + position.width, innerWidth - position.left);
        var height = element.offsetHeight;
        //如果高度超出可视区，调整高度
        if (height > maxHeight) {
            css(element, { height: fromOffset(maxHeight) });
        }

        css(element, `min-width:auto;`);
        var aimedWidth = element.offsetWidth;
        //如果宽度不足其附着元素的宽度
        if (aimedWidth < target.offsetWidth) {
            aimedWidth = target.offsetWidth;
        }

        //如果宽度超出可视区，调整宽度
        if (aimedWidth > maxWidth) {
            aimedWidth = maxWidth;
        }
        var side;
        if (aimedWidth !== element.offsetWidth) {
            css(element, { width: fromOffset(aimedWidth) });
        }
        if (position.top + element.offsetHeight + position.height > innerHeight) {
            side = 'bottom';
            var temp = `bottom:${fromOffset(viewrect.height - position.top + viewrect.top)};top:auto;`;
        } else {
            side = 'top';
            var temp = `top:${fromOffset(position.top - viewrect.top + position.height)};bottom:auto;`
        }
        css(element, temp);
        if (_rhomb) {
            css(_rhomb, temp);
            _rhomb.setSide(side);
        }
        if (_rhomb) {
            var targetX = position.left + (position.width - element.offsetWidth) / 2;
            if (targetX < 0) {
                css(element, `left:0;right:auto`);
                css(_rhomb, `left:${fromOffset(position.left + position.width / 2)};right:auto`);
            }
            else if (targetX + element.offsetWidth > innerWidth) {
                css(element, `right:0;left:auto`);
                css(_rhomb, `right:${fromOffset(innerWidth - position.left - position.width / 2)};left:auto`);
            }
            else {
                css(element, `left:${fromOffset(targetX)};right:auto`);
                css(_rhomb, `left:${fromOffset(position.left + position.width / 2)};right:auto`);

            }
        } else {
            if (position.left + element.offsetWidth > innerWidth) {
                css(element, `right:${fromOffset(viewrect.width + viewrect.left - position.left - position.width)};left:auto;`);
            }
            else {
                css(element, `left:${fromOffset(position.left - viewrect.left)};right:auto;`);
            }
        }

        var offsetParent = target.offsetParent;
        if (offsetParent) {
            if (overlap(target, offsetParent) > 0) {
                element.style.display = element.origin.display;
                if (_rhomb && !_rhomb.parentNode) {
                    appendChild.before(element, _rhomb)
                }
            } else {
                element.style.display = "none";
                remove(_rhomb);
            };
        } else if (_rhomb && !_rhomb.parentNode) {
            appendChild.before(element, _rhomb)
        }
    }
    if (document.addEventListener) {
        document.addEventListener("scroll", reshape, true);
        window.addEventListener("resize", reshape);
    }
    reshape();
    lazy(reshape)();
};
var _as_xextra = arriswise(_as_yextra, arguments);
var popup_as_single = function (element) {
    css(element, `z-index:${zIndex()};`);
    global(element, false);
};
var popup_to_point = function (element, [x, y]) {
    if (x <= 1 && y <= 1 && x >= 0 && y >= 0) {
        popup_as_single(element);
        move.bindPosition(element, [x, y]);
    } else {
        x = calcPixel(x), y = calcPixel(y);
        popup_fixup(element, x, y);
    }
};
var popup_fixup = function (element, x, y) {
    css(element, {
        position: 'absolute',
    });
    popup_as_single(element);
    var left = "left", top = 'top';
    if (y + element.offsetHeight > window.innerHeight && y << 1 > window.innerHeight) {
        top = "bottom";
        y = window.innerHeight - y;
    }
    if (x + element.offsetWidth > window.innerWidth && x << 1 > window.innerWidth) {
        left = "right";
        x = window.innerWidth - x;
    }
    css(element, {
        [left]: fromOffset(x),
        [top]: fromOffset(y)
    });

};
var popup_to_event = function (element, { clientX, clientY }) {
    popup_fixup(element, clientX, clientY);
};
var global = function (element, issingle) {
    once("remove")(element, cleanup);
    rootElements.push(element);
    popup.global &&
        issingle !== false ? popup.global(element, true) : appendChild(document.body, element);
};
var popup_as_yextra = _as_yextra.bind(null, global, innerWidth, innerHeight);
var popup_as_xextra = _as_xextra.bind(null, global, innerHeight, innerWidth);
var cleanup = new Cleanup(rootElements);<commit_msg>修改计算弹出位置的方法<commit_after>/**
 * 弹出层
 */
onkeydown(document, function (e) {
    if (e.which === 27 && rootElements.length) {
        var r = rootElements.pop();
        r.blur();
        remove(r);
    }
});
var animationStyle = "opacity:0;transform:scale(1.2);transition:.1s opacity ease-out,.2s transform ease-out;";
var setInitialStyle = function (element) {
    if (!element.initialStyle) element.initialStyle = animationStyle;
};
var windowFactory = function (window) {
    css(window, `min-height:10px;min-width:10px;position:absolute;`);
    return window;
};
var loadingFactory = function (element) {
    css(element, `position:absolute;`);
    return element;
}
/**
 * 弹出子窗口的函数
 * @param {string} path 窗口路径的唯一索引
 * @param {any} parameters 给窗口的作域预先赋值
 * @param {object} style 自定义样式，主要是width,height等位置参数
 * @returns {Element} 给调用弹窗的命令暴露的可选方法
 */
var popup = function (path) {
    if (isString(path)) {
        return popup_path.apply(null, arguments);
    }
    if (isNode(path)) {
        return popup_view.apply(null, arguments);
    }
    throw new Error(`path isn't valid:${path}`);
};
var popup_path = function (path = "", parameters, target) {
    if (!popup.go || !popup.prepare) throw new Error("当前环境无法使用");
    // 3 has mask has view control
    var element;
    if (/^[#\+]/.test(path)) {
        // mask
        path = path.replace(/^[#\+]/, "");
        var load = function () {
            windowFactory(element);
            setInitialStyle(element);
            popup_with_mask(element, target);
            if (element.dragHandle) drag.on(element);
        };
    }
    // 2 has view control has no mask
    else if (/^@/.test(path)) {
        path = path.replace(/^@/, "");
        var load = function () {
            windowFactory(element);
            setInitialStyle(element);
            popup_as_single(element);
            if (element.dragHandle) drag.on(element);
        };
    }
    // 1 has mask has no control
    else if (/^!/.test(path)) {
        path = path.replace(/^!/, "");
        var load = function () {
            loadingFactory(element);
            setInitialStyle(element);
            popup_with_mask(element);
        };
    }
    // 0 has no mask no control
    else {
        var load = function () {
            loadingFactory(element);
            if (isNode(target)) {
                popup_as_extra(element, target);
            }
            else if (target instanceof Event) {
                popup_to_event(element, target);
            } else if (target instanceof Array) {
                popup_to_point(element, target);
            } else if (target) {
                popup_with_mask(element);
            } else {
                if (parameters !== false && target !== false) setInitialStyle(element);
                popup_as_single(element);
            }
        };
    }
    var fullfill = function () {
        remove(element);
        remove(element && element.mask);
        element = popup.create(path, parameters);
        if (!element) return;
        load();
        element.$reload = fullfill;
        if (!target && target !== false && parameters !== false) {
            element.style.opacity = 0;
            if (element.parentNode) move.bindPosition(element, [.5, .5]), element.style.opacity = 1;
            else once('append')(element, function () {
                move.bindPosition(element, [.5, .5]);
                element.style.opacity = 1;
            });
        }
    };
    popup.prepare(path, fullfill);
    return element;
};

var popup_view = function (element, target, style) {
    if (isNode(target)) {
        if (target.isMask) {
            popup_with_mask(element, target);
            return element;
        }
        return popup_as_extra(element, target, style);
    }
    if (target instanceof Event) {
        popup_to_event(element, target);
    } else if (target instanceof Array) {
        popup_to_point(element, target);
    } else if (target) {
        popup_with_mask(element);
    } else {
        popup_as_single(element);
    }
    return element;
};

css(".mask", `position:absolute;position:fixed;left:0;right:0;bottom:0;top:0;width:auto;height:auto;background:#000;opacity:0.2;transform:scale(1);display:block`);
var createMask = function (element) {
    var masks = element.with;
    if (masks) for (var cx = 0, dx = masks.length; cx < dx; cx++) {
        var m = masks[cx];
        if (m.isMask) return m;
    }
    var mask = document.createElement("mask");
    mask.initialStyle = animationStyle;
    css(mask, `z-index:${zIndex()};`);
    mask.className = "mask";
    mask.isMask = true;
    return mask;
};
var popup_with_mask = function (element, mask = createMask(element)) {
    if (!mask.clean) {
        mask.clean = new Cleanup(element.with);
    }
    onremove(element, mask.clean);
    css(element, `z-index:${zIndex()};`);
    if (mask.parentNode) {
        global(element, false);
    }
    if (!element.with) {
        element.with = [mask];
    } else if (element.with instanceof Array) {
        if (!~element.with.indexOf(element)) element.with.push(mask);
    } else {
        if (element.with !== element) element.with = [element.with, mask];
    }
    if (!element.parentNode) global(element, false);
    return element;
};
var isypop = function (target) {
    if (!target) return false;
    var { offsetParent, nextSibling, previousSibling } = target;
    if (
        nextSibling && nextSibling.tagName === target.tagName
        && (
            nextSibling.offsetLeft - target.offsetLeft >= target.offsetWidth / 2
            || target.offsetLeft - nextSibling.offsetLeft >= nextSibling.offsetWidth / 2
        )
        || previousSibling && previousSibling.tagName === target.tagName
        && (
            previousSibling.offsetLeft - target.offsetLeft >= target.offsetWidth / 2
            || target.offsetLeft - previousSibling.offsetLeft >= previousSibling.offsetWidth / 2
        )
    ) return true;
    var padding = parseFloat(getComputedStyle(offsetParent).paddingTop) + parseFloat(getComputedStyle(offsetParent).paddingBottom);
    if (offsetParent && target.offsetTop / target.offsetHeight < .2 && (offsetParent.clientWidth - padding) / target.offsetWidth > 1.5) return true;

};
var isxpop = arriswise(isypop, arguments);
var popup_as_extra = function (element, target, style) {
    if (style) {
        if (/^[vy]/i.test(style)) {
            popup_as_yextra(element, target, style);
            return;
        }
        if (/^[hx]/i.test(style)) {
            popup_as_xextra(element, target, style);
            return;
        }

    }
    if (isypop(target)) {
        popup_as_yextra(element, target, style);
    } else if (isxpop(target)) {
        console.log(target)
        popup_as_xextra(element, target, style);
    } else if (isypop(target.offsetParent)) {
        popup_as_yextra(element, target, style);
    } else if (isxpop(target.offsetParent)) {
        popup_as_xextra(element, target, style);
    } else {
        if (/inline|cell/i.test(getComputedStyle(target).display)) {
            popup_as_yextra(element, target, style);
        } else {
            popup_as_xextra(element, target, style);
        }
    }

};
var _as_yextra = function (global, innerWidth, innerHeight, element, target, pointer) {
    var withRhomb = pointer && /rhomb|rhombus|diamond|lozenge|triangle|[drlt]$/i.test(pointer);
    if (withRhomb) {
        var _rhomb = rhomb();
    }
    if (!element.origin) {
        element.origin = {
            height: element.style.height,
            width: element.style.width,
            minWidth: element.style.minWidth,
            display: element.style.display
        };
    }
    var zindex = zIndex();
    css(element, `position:absolute;z-index:${zindex}`);
    css(_rhomb, { zIndex: zindex });
    var release1 = onremove(target, function () {
        remove(element);
    });
    var release2 = onremove(element, function () {
        release1();
        release2();
        remove(_rhomb);
        if (document.removeEventListener) {
            document.removeEventListener("scroll", reshape, true);
            window.removeEventListener("resize", reshape);
        }
    });
    global(element, false);
    var reshape = function () {
        extend(element.style, element.origin);
        var position = getScreenPosition(target);
        var viewrect = getScreenPosition(element.offsetParent);
        var documentbody = document.body;
        if (element.offsetParent === documentbody && !/^(absolute|relative|fixed)$/i.test(getComputedStyle(documentbody).position)) {
            viewrect.left = 0;
            viewrect.top = 0;
            viewrect.right += innerWidth - viewrect.right;
            viewrect.bottom += innerHeight - viewrect.bottom;
            viewrect.width = viewrect.right - viewrect.left;
            viewrect.height = viewrect.bottom - viewrect.top;
        } else {
            viewrect.top += documentbody.clientTop;
            viewrect.left += documentbody.clientLeft;
            viewrect.height = documentbody.clientHeight;
            viewrect.width = documentbody.clientWidth;
            viewrect.right = viewrect.left + viewrect.width;
            viewrect.bottom = viewrect.top + viewrect.height;
        }
        var maxHeight = Math.max(position.top, innerHeight - position.top - position.height);
        var maxWidth = Math.max(position.left + position.width, innerWidth - position.left);
        var height = element.offsetHeight;
        //如果高度超出可视区，调整高度
        if (height > maxHeight) {
            css(element, { height: fromOffset(maxHeight) });
        }

        css(element, `min-width:auto;`);
        var aimedWidth = element.offsetWidth;
        //如果宽度不足其附着元素的宽度
        if (aimedWidth < target.offsetWidth) {
            aimedWidth = target.offsetWidth;
        }

        //如果宽度超出可视区，调整宽度
        if (aimedWidth > maxWidth) {
            aimedWidth = maxWidth;
        }
        var side;
        if (aimedWidth !== element.offsetWidth) {
            css(element, { width: fromOffset(aimedWidth) });
        }
        if (position.top + element.offsetHeight + position.height > innerHeight) {
            side = 'bottom';
            var temp = `bottom:${fromOffset(viewrect.height - position.top + viewrect.top)};top:auto;`;
        } else {
            side = 'top';
            var temp = `top:${fromOffset(position.top - viewrect.top + position.height)};bottom:auto;`
        }
        css(element, temp);
        if (_rhomb) {
            css(_rhomb, temp);
            _rhomb.setSide(side);
        }
        if (_rhomb) {
            var targetX = position.left + (position.width - element.offsetWidth) / 2;
            if (targetX < 0) {
                css(element, `left:0;right:auto`);
                css(_rhomb, `left:${fromOffset(position.left + position.width / 2)};right:auto`);
            }
            else if (targetX + element.offsetWidth > innerWidth) {
                css(element, `right:0;left:auto`);
                css(_rhomb, `right:${fromOffset(innerWidth - position.left - position.width / 2)};left:auto`);
            }
            else {
                css(element, `left:${fromOffset(targetX)};right:auto`);
                css(_rhomb, `left:${fromOffset(position.left + position.width / 2)};right:auto`);

            }
        } else {
            if (position.left + element.offsetWidth > innerWidth) {
                css(element, `right:${fromOffset(viewrect.width + viewrect.left - position.left - position.width)};left:auto;`);
            }
            else {
                css(element, `left:${fromOffset(position.left - viewrect.left)};right:auto;`);
            }
        }

        var offsetParent = target.offsetParent;
        if (offsetParent) {
            if (overlap(target, offsetParent) > 0) {
                element.style.display = element.origin.display;
                if (_rhomb && !_rhomb.parentNode) {
                    appendChild.before(element, _rhomb)
                }
            } else {
                element.style.display = "none";
                remove(_rhomb);
            };
        } else if (_rhomb && !_rhomb.parentNode) {
            appendChild.before(element, _rhomb)
        }
    }
    if (document.addEventListener) {
        document.addEventListener("scroll", reshape, true);
        window.addEventListener("resize", reshape);
    }
    reshape();
    lazy(reshape)();
};
var _as_xextra = arriswise(_as_yextra, arguments);
var popup_as_single = function (element) {
    css(element, `z-index:${zIndex()};`);
    global(element, false);
};
var popup_to_point = function (element, [x, y]) {
    if (x <= 1 && y <= 1 && x >= 0 && y >= 0) {
        popup_as_single(element);
        move.bindPosition(element, [x, y]);
    } else {
        x = calcPixel(x), y = calcPixel(y);
        popup_fixup(element, x, y);
    }
};
var popup_fixup = function (element, x, y) {
    css(element, {
        position: 'absolute',
    });
    popup_as_single(element);
    var left = "left", top = 'top';
    if (y + element.offsetHeight > window.innerHeight && y << 1 > window.innerHeight) {
        top = "bottom";
        y = window.innerHeight - y;
    }
    if (x + element.offsetWidth > window.innerWidth && x << 1 > window.innerWidth) {
        left = "right";
        x = window.innerWidth - x;
    }
    css(element, {
        [left]: fromOffset(x),
        [top]: fromOffset(y)
    });

};
var popup_to_event = function (element, { clientX, clientY }) {
    popup_fixup(element, clientX, clientY);
};
var global = function (element, issingle) {
    once("remove")(element, cleanup);
    rootElements.push(element);
    popup.global &&
        issingle !== false ? popup.global(element, true) : appendChild(document.body, element);
};
var popup_as_yextra = _as_yextra.bind(null, global, innerWidth, innerHeight);
var popup_as_xextra = _as_xextra.bind(null, global, innerHeight, innerWidth);
var cleanup = new Cleanup(rootElements);<|endoftext|>


<commit_before>var express = require('express'),
  http = require('http'),
  corsMiddleware = require('cors'),
  app = express(),
  master = require('./server/master.js');

var envPort = process.env.PORT || 8001;
var server = app.listen(port, function() {
  console.log('Listening on port %d', server.address().port);
});

var io = require('socket.io').listen(server, {
  origins: '*:*'
});

io.on('connection', function(socket) {
  //if existing servers are open..
  socket.emit('hello', {
    stopped: master.isStopped()
  });
  socket.on('hello', function(data) {
    console.log('hello ', data.deviceId);
    if (!data.deviceId) {
      console.error('device attempted to connect without an id');
      return;
    }
    var device = master.matchDevice(data.deviceId, socket);
    if (master.isStopped()) {
      //must match with the device before returning
      return;
    }
    socket.emit('update', {
      moves: master.getMoves(),
      lastMoveHash: device.lastMoveHash
    });
  });
  socket.on('event', function(data) {
    if (master.isStopped()) {
      return;
    }
    console.log('update recieved: ', data.deviceId, data.hash, data.type);
    master.onUpdate(data);
  });
  socket.on('playMove', function(data) {
    console.log('move repeated: ', data.deviceId, data.hash, data.type);
    if (master.isStopped()) {
      return;
    }
    var device = master.matchDevice(data.deviceId, socket);
    if (device) {
      device.lastMoveHash = data.hash;
    }
  });
  socket.on('reset', function(data) {
    if (!data) {
      data = {};
    }
    console.log('reset');
    master.reset(data);
  });
  socket.on('stop', function(data) {
    console.log('stop');
    master.stop();
  });
  socket.on('start', function(data) {
    if (!data) {
      data = {};
    }
    master.reset(data);
  });
});
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Credentials", "true");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  res.header("Access-Control-Allow-Headers", 'Content-Type, Accept, Origin, Cookie');
  res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
  next();
});
app.options('*', function(req, res) {
  res.send(' ');
});

//todo: break these out
app.get('/jquery', function(req, res) {
  res.sendfile(__dirname + '/node_modules/jquery/dist/jquery.js');
});
app.get('/jquery.cookie', function(req, res) {
  res.sendfile(__dirname + '/node_modules/jquery.cookie/jquery.cookie.js');
});
app.get('/socket.js-client', function(req, res) {
  res.sendfile(__dirname + '/node_modules/socket.io-client/socket.io.js');
});
app.get('/helpers', function(req, res) {
  res.sendfile(__dirname + '/client/helpers.js');
});
app.get('/master-command', function(req, res) {
  res.sendfile(__dirname + '/client/master.js');
});<commit_msg>logging/quickly debugging a production issue<commit_after>var express = require('express'),
  http = require('http'),
  corsMiddleware = require('cors'),
  app = express(),
  master = require('./server/master.js');

var envPort = process.env.PORT || 8001;
var server = app.listen(envPort, function() {
  console.log('Listening on port %d', server.address().port);
});

var io = require('socket.io').listen(server, {
  origins: '*:*'
});

io.on('connection', function(socket) {
  //if existing servers are open..
  socket.emit('hello', {
    stopped: master.isStopped()
  });
  socket.on('hello', function(data) {
    console.log('hello ', data.deviceId);
    if (!data.deviceId) {
      console.error('device attempted to connect without an id');
      return;
    }
    var device = master.matchDevice(data.deviceId, socket);
    if (master.isStopped()) {
      //must match with the device before returning
      return;
    }
    socket.emit('update', {
      moves: master.getMoves(),
      lastMoveHash: device.lastMoveHash
    });
  });
  socket.on('event', function(data) {
    if (master.isStopped()) {
      return;
    }
    console.log('update recieved: ', data.deviceId, data.hash, data.type);
    master.onUpdate(data);
  });
  socket.on('playMove', function(data) {
    console.log('move repeated: ', data.deviceId, data.hash, data.type);
    if (master.isStopped()) {
      return;
    }
    var device = master.matchDevice(data.deviceId, socket);
    if (device) {
      device.lastMoveHash = data.hash;
    }
  });
  socket.on('reset', function(data) {
    if (!data) {
      data = {};
    }
    console.log('reset');
    master.reset(data);
  });
  socket.on('stop', function(data) {
    console.log('stop');
    master.stop();
  });
  socket.on('start', function(data) {
    if (!data) {
      data = {};
    }
    master.reset(data);
  });
});
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Credentials", "true");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  res.header("Access-Control-Allow-Headers", 'Content-Type, Accept, Origin, Cookie');
  res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
  next();
});
app.options('*', function(req, res) {
  res.send(' ');
});

//todo: break these out
app.get('/jquery', function(req, res) {
  res.sendfile(__dirname + '/node_modules/jquery/dist/jquery.js');
});
app.get('/jquery.cookie', function(req, res) {
  res.sendfile(__dirname + '/node_modules/jquery.cookie/jquery.cookie.js');
});
app.get('/socket.js-client', function(req, res) {
  res.sendfile(__dirname + '/node_modules/socket.io-client/socket.io.js');
});
app.get('/helpers', function(req, res) {
  res.sendfile(__dirname + '/client/helpers.js');
});
app.get('/master-command', function(req, res) {
  res.sendfile(__dirname + '/client/master.js');
});<|endoftext|>


<commit_before>import axios from 'axios';
const ROOT_URL = 'https://api.cryptonator.com/api/ticker/';
const TOP_URL = 'https://api.coinmarketcap.com/v1/ticker/?limit=20'
const TEST_URL= `https://cors-anywhere.herokuapp.com/http://api.coinmarketcap.com/v1/ticker/`;
const RANDOM_URL = 'https://www.cryptocompare.com/api/data/coinsnapshotfullbyid/?id='
const API_URL = 'http://localhost:3090;'

export const FETCH_CRYPTO = 'FETCH_CRYPTO';
export const TOP_CRYPTO = 'TOP_CRYPTO';
export const RANDOM_CRYPTO = 'RANDOM_CRYPTO';

export function fetchCrypto(ticker){
  const url = `${TEST_URL}${ticker}`;
  const request = axios.get(url);
  console.log('Request',request);

  return {
    type: FETCH_CRYPTO,
    payload: request,
  };
}

export function topCrypto(){
  const request = axios.get(TOP_URL);
  console.log('Request', request);
  return {
    type: TOP_CRYPTO,
    payload: request,
  };
}

export function randomCrypto(id){
  const url = `${RANDOM_URL}${id}`
  const request = axios.get(url);
  console.log('Request',request);

  return{
    type:RANDOM_CRYPTO,
    payload: request,
  }
}

export function signinUser( {email, password }){
  return function(dispatch){
    // submit email pw to server
    axios.post(`${API_URL}/signin`, {email, password});
    // if request is good
    // -update state to indicate user is authenticated
    // save jwt token
    // redirect to new route
    // if request is bad
    // show an error to the user
}

}
<commit_msg>add item to fix cors issue<commit_after>import axios from 'axios';
const ROOT_URL = 'https://api.cryptonator.com/api/ticker/';
const TOP_URL = 'https://api.coinmarketcap.com/v1/ticker/?limit=20'
const TEST_URL= `https://cors-anywhere.herokuapp.com/http://api.coinmarketcap.com/v1/ticker/`;
const RANDOM_URL = 'https://cors-anywhere.herokuapp.com/http://www.cryptocompare.com/api/data/coinsnapshotfullbyid/?id='
const API_URL = 'http://localhost:3090;'

export const FETCH_CRYPTO = 'FETCH_CRYPTO';
export const TOP_CRYPTO = 'TOP_CRYPTO';
export const RANDOM_CRYPTO = 'RANDOM_CRYPTO';

export function fetchCrypto(ticker){
  const url = `${TEST_URL}${ticker}`;
  const request = axios.get(url);
  console.log('Request',request);

  return {
    type: FETCH_CRYPTO,
    payload: request,
  };
}

export function topCrypto(){
  const request = axios.get(TOP_URL);
  console.log('Request', request);
  return {
    type: TOP_CRYPTO,
    payload: request,
  };
}

export function randomCrypto(id){
  const url = `${RANDOM_URL}${id}`
  const request = axios.get(url);
  console.log('Request',request);

  return{
    type:RANDOM_CRYPTO,
    payload: request,
  }
}

export function signinUser( {email, password }){
  return function(dispatch){
    // submit email pw to server
    axios.post(`${API_URL}/signin`, {email, password});
    // if request is good
    // -update state to indicate user is authenticated
    // save jwt token
    // redirect to new route
    // if request is bad
    // show an error to the user
}

}
<|endoftext|>


<commit_before>// Top-level container object for this js file.
var NER = {};

// "nodes" is a table of entity names, mapping to an array position generated
// uniquely by the "counter" variable.  Once the table is complete, the nodes
// table can be recast into an array.
NER.nodes = {};
//NER.docs = {};
NER.links = {};
NER.counter = 0;

// This count will signal when the last ajax request has completed, and graph
// assembly can continue.
NER.num_files = 0;
NER.files_processed = 0;

function assembleGraph(){
    // Create a graph object.
    var g = {};

    // Copy the links over into an array within the graph object.
    g.links = [];
    $.each(NER.links, function(k, v){
       g.links.push(v);
    });

    // Do the same for the nodes, but do two additional things:
    //
    // 1. When extracting an object from the NER.nodes table, remove the "index"
    //    property, as it will no longer be needed after final placement of the
    //    node.
    //
    // 2. Place the node object into the place in the array indexed by that
    //    "index" property.  This ensures that the references in the link list
    //    are to the proper nodes.
    //
    // Start by creating an empty array of length equal to the number of total
    // entities.
    g.nodes = Array(Object.keys(NER.nodes).length);

    // Now plop each entity into its proper place.
    $.each(NER.nodes, function(k, v){
        var i = v.index;
        delete v.index;
        g.nodes[i] = v;
    });

    console.log(g);
}

function handleFileSelect(evt){
    // Grab the list of files selected by the user.
    var files = evt.target.files;

    // Compute how many of these files will actually be processed for named
    // entities (see comment below for explanation of how the files are vetted).
    NER.num_files = 0;
    $.each(files, function(k, v){
        if(v.type == '(n/a)' || v.type.slice(0,5) == 'text/'){
            NER.num_files++;
        }
    });
    console.log(NER.num_files + " files to process");

    // Now run through the files, using a callback to load the content from the
    // proper ones and pass it to an ajax call to perform named-entity
    // recognition.
    var output = [];
    for(var i=0; i<files.length; i++){
        var f = files[i];

        // Decide whether to process a selected file or not - accept everything
        // with a mime-type of text/*, as well as those with unspecified type
        // (assume the user knows what they are doing in such a case).
        var using = null;
        if(f.type == '(n/a)'){
            status = "accepted";
            msg = "ok, assuming text";
            using = true;
        }
        else if(f.type.slice(0,5) == 'text/'){
            status = "accepted";
            msg = "ok";
            using = true;
        }
        else{
            status = "rejected";
            msg = "rejected";
            using = false;
        }
        output.push('
', escape(f.name), ' (', f.type || 'n/a', ') - ', f.size, ' bytes ', f.type == 'text/plain' ? '(ok)' : '(rejected)');

        if(using){
            var reader = new FileReader();
            reader.onload = (function(file){
                return function(e){
                    var filename = escape(file.name);
                    console.log(filename);

                    // Grab the text of the file.
                    var text = e.target.result;

                    // Create a "progress" bullet point describing the current
                    // AJAX state of this file.
                    var elem = document.createElement("li");
                    elem.innerHTML = "processing " + filename;
                    elem.setAttribute("id", filename.replace(".","-"));
                    elem.setAttribute("class", "processing inprogress");
                    $("#blobs").get(0).appendChild(elem);

                    // Fire an AJAX call to retrieve the named entities in the
                    // document.
                    $.ajax({
                        url: '/service/NER',
                        data: {
                            text: text
                        },
                        dataType: 'text',
                        success: function(data){
                            console.log("success for " + filename + " - result: " + data);
                            $("#" + filename.replace(".","-")).removeClass("inprogress").addClass("done").get(0).innerHTML = filename + " processed";

                            // Create an entry for the document itself.
                            NER.nodes[filename] = {
                                node: filename,
                                type: "DOCUMENT",
                                count: 0,
                                index: NER.counter++
                            };
                            var doc_index = NER.counter - 1;

                            // Extract the JSON object from the AJAX response.
                            var entities = $.parseJSON(data);

                            // Process the entities.
                            $.each(entities, function(i, e){
                                // Place the entity into the global entity list
                                // if not already there.
                                //
                                // Also update the count of this entity.
                                var key = '["' + e[0] + '","' + e[1] + '"]';
                                if(!NER.nodes.hasOwnProperty(key)){
                                    NER.nodes[key] = {
                                        node: e[1],
                                        type: e[0],
                                        count: 1,
                                        index: NER.counter++
                                    }
                                }
                                else{
                                    NER.nodes[key].count++;
                                }
                                var entity_index = NER.nodes[key].index;

                                // Enter a link into the link list, or just
                                // increase the weight if the link exists
                                // already.
                                var link = "(" + entity_index + "," + doc_index + ")";
                                if(!NER.links.hasOwnProperty(link)){
                                    NER.links[link] = {
                                        source: entity_index,
                                        target: doc_index,
                                        weight: 1
                                    };
                                }
                                else{
                                    NER.links[link].weight++;
                                }
                            });

                            console.log(NER.nodes);

                            // Increment the number of successfully processed
                            // files; if the number reaches the number of total
                            // files to process, launch the final step of
                            // assembling the graph.
                            ++NER.files_processed;
                            console.log(NER.files_processed + " of " + NER.num_files + " processed");
                            console.log(NER.files_processed == NER.num_files);
                            if(NER.files_processed == NER.num_files){
                                console.log("calling assembleGraph()");
                                assembleGraph();
                            }
                        },
                        error: function(){
                            console.log("error for " + filename);
                            $("#" + filename.replace(".","-")).removeClass("inprogress").addClass("failed").get(0).innerHTML = filename + " processed";
                        }
                    });
                }
            })(f);

            reader.readAsText(f);
        }
    }
    document.getElementById('file-info').innerHTML = '
' + output.join('') + '
';
}

window.onload = function(){
    document.getElementById('docs').addEventListener('change', handleFileSelect, false);
}
<commit_msg>reformatted; removed some console logging; added abstraction for creation of D3 rendering<commit_after>// Top-level container object for this js file.
var NER = {};

// "nodes" is a table of entity names, mapping to an array position generated
// uniquely by the "counter" variable.  Once the table is complete, the nodes
// table can be recast into an array.
NER.nodes = {};
//NER.docs = {};
NER.links = {};
NER.counter = 0;

// This count will signal when the last ajax request has completed, and graph
// assembly can continue.
NER.num_files = 0;
NER.files_processed = 0;

function assembleGraph(){
    // Create a graph object.
    var g = {};

    // Copy the links over into an array within the graph object.
    g.links = [];
    $.each(NER.links, function(k, v){
        g.links.push(v);
    });

    // Do the same for the nodes, but do two additional things:
    //
    // 1. When extracting an object from the NER.nodes table, remove the "index"
    //    property, as it will no longer be needed after final placement of the
    //    node.
    //
    // 2. Place the node object into the place in the array indexed by that
    //    "index" property.  This ensures that the references in the link list
    //    are to the proper nodes.
    //
    // Start by creating an empty array of length equal to the number of total
    // entities.
    g.nodes = Array(Object.keys(NER.nodes).length);

    // Now plop each entity into its proper place.
    $.each(NER.nodes, function(k, v){
        var i = v.index;
        delete v.index;
        g.nodes[i] = v;
    });

    return g;
}

function renderGraph(g){
    //console.log("renderGraph(): g = " + g.nodes.join(",") + g.links.join(","));
    console.log("renderGraph(): g = " + JSON.stringify(g));
}

function handleFileSelect(evt){
    // Grab the list of files selected by the user.
    var files = evt.target.files;

    // Compute how many of these files will actually be processed for named
    // entities (see comment below for explanation of how the files are vetted).
    NER.num_files = 0;
    $.each(files, function(k, v){
        if(v.type == '(n/a)' || v.type.slice(0,5) == 'text/'){
            NER.num_files++;
        }
    });
    console.log(NER.num_files + " files to process");

    // Now run through the files, using a callback to load the content from the
    // proper ones and pass it to an ajax call to perform named-entity
    // recognition.
    var output = [];
    for(var i=0; i<files.length; i++){
        var f = files[i];

        // Decide whether to process a selected file or not - accept everything
        // with a mime-type of text/*, as well as those with unspecified type
        // (assume the user knows what they are doing in such a case).
        var using = null;
        if(f.type == '(n/a)'){
            status = "accepted";
            msg = "ok, assuming text";
            using = true;
        }
        else if(f.type.slice(0,5) == 'text/'){
            status = "accepted";
            msg = "ok";
            using = true;
        }
        else{
            status = "rejected";
            msg = "rejected";
            using = false;
        }
        output.push('
', escape(f.name), ' (', f.type || 'n/a', ') - ', f.size, ' bytes ', f.type == 'text/plain' ? '(ok)' : '(rejected)');

        if(using){
            var reader = new FileReader();
            reader.onload = (function(file){
                return function(e){
                    var filename = escape(file.name);
                    console.log(filename);

                    // Grab the text of the file.
                    var text = e.target.result;

                    // Create a "progress" bullet point describing the current
                    // AJAX state of this file.
                    var elem = document.createElement("li");
                    elem.innerHTML = "processing " + filename;
                    elem.setAttribute("id", filename.replace(".","-"));
                    elem.setAttribute("class", "processing inprogress");
                    $("#blobs").get(0).appendChild(elem);

                    // Fire an AJAX call to retrieve the named entities in the
                    // document.
                    $.ajax({
                        url: '/service/NER',
                        data: {
                            text: text
                        },
                        dataType: 'text',
                        success: function(data){
                            console.log("success for " + filename);
                            $("#" + filename.replace(".","-")).removeClass("inprogress").addClass("done").get(0).innerHTML = filename + " processed";

                            // Create an entry for the document itself.
                            NER.nodes[filename] = {
                                node: filename,
                        type: "DOCUMENT",
                        count: 1,
                        index: NER.counter++
                            };
                            var doc_index = NER.counter - 1;

                            // Extract the JSON object from the AJAX response.
                            var entities = $.parseJSON(data);

                            // Process the entities.
                            $.each(entities, function(i, e){
                                // Place the entity into the global entity list
                                // if not already there.
                                //
                                // Also update the count of this entity.
                                var key = '["' + e[0] + '","' + e[1] + '"]';
                                if(!NER.nodes.hasOwnProperty(key)){
                                    NER.nodes[key] = {
                                        node: e[1],
                                type: e[0],
                                count: 1,
                                index: NER.counter++
                                    }
                                }
                                else{
                                    NER.nodes[key].count++;
                                }
                            var entity_index = NER.nodes[key].index;

                            // Enter a link into the link list, or just
                            // increase the weight if the link exists
                            // already.
                            var link = "(" + entity_index + "," + doc_index + ")";
                            if(!NER.links.hasOwnProperty(link)){
                                NER.links[link] = {
                                    source: entity_index,
                                    target: doc_index,
                                    weight: 1
                                };
                            }
                            else{
                                NER.links[link].weight++;
                            }
                            });

                            //console.log(NER.nodes);

                            // Increment the number of successfully processed
                            // files; if the number reaches the number of total
                            // files to process, launch the final step of
                            // assembling the graph.
                            ++NER.files_processed;
                            console.log(NER.files_processed + " of " + NER.num_files + " processed");

                            if(NER.files_processed == NER.num_files){
                                console.log("calling assembleGraph()");
                                var graph = assembleGraph();
                                renderGraph(graph);
                            }
                        },
                        error: function(){
                            console.log("error for " + filename);
                            $("#" + filename.replace(".","-")).removeClass("inprogress").addClass("failed").get(0).innerHTML = filename + " processed";
                        }
                    });
                }
            })(f);

            reader.readAsText(f);
        }
    }
    document.getElementById('file-info').innerHTML = '
' + output.join('') + '
';
}

window.onload = function(){
    document.getElementById('docs').addEventListener('change', handleFileSelect, false);
}
<|endoftext|>


<commit_before>/**
 * Provides the "WHAT'S NEW WITH GDPE?" dialog displaying GDP changes when the party is
 * started after a new version is released.
 */
gdpChangeLog = {
    /* Display the change log for the user if either there is no cached
       version number in chrome storage or if the cached version doesn't
       match the current GDP extension version in the manifest. Don't
       display the change log if the manifest cannot be fetched */
    checkForUpdates: function() {
        try {
            // fetch manifest to get version number
            var manifest = chrome.runtime.getManifest()
        } catch (e) {
            // supposedly getManifest doesn't work for versions of Chrome less
            // than 22 so don't bother with the versions
            return;
        }

        var currentVersion = manifest.version;

        // fetch the cached version from chrome storage
        chrome.storage.sync.get("cachedVersion", function(storageItem) {
            if (currentVersion !== storageItem['cachedVersion']) {
                // update the cached version to be the current version
                chrome.storage.sync.set({
                    cachedVersion: currentVersion
                });
                // open the "What's New?" change log dialog
                gdpChangeLog.openChangeLog();
            }
        });
    },
    /* Remove the change log dialog if it is open */
    closeChangeLog: function() {
        $('.gdp-change-log-background').remove();
    },
    /* Open the change log dialog on the page. Clicking the CONTINUE
       button or clicking outside the dialog will close the dialog. */
    openChangeLog: function() {
        // transparent full screen div that contains the version changes, but
        // will close on click
        var gdpChangeLogBackground = $('

')
            .css('z-index', maxZIndex)
            .click(function() {
                $(this).remove();
            });

        // actual dialog displaying all of the content
        var gdpChangeLogDialog = $('

')
            .click(function(event) {
                event.stopPropagation();
            });

        // "WHAT'S NEW WITH GDP" dialog header
        var gdpChangeLogHeader = ['
',
                                      '',
                                      "WHAT'S NEW WITH GDPE?",
                                      '',
                                  '
'].join('');

        // body of dialog containing version changes
        var gdpChangeLogBody = $('

');

        // loop through each release version
        for (var i = 0; i < gdpChangeLog.updates.length; i++) {
            var currentLogDiv = $('

');
            var currentLog = gdpChangeLog.updates[i];

            // displays release date of version
            var logDate = ['
',
                               currentLog.date,
                           '
'].join('');
            currentLogDiv.append(logDate);

            // displays all changes for the current version
            var logDescription = $('

');

            // loop through each individual change within the current version
            for (var j = 0; j < currentLog.description.length; j++) {
                var logChange = ['
',
                                     currentLog.description[j],
                                 '
'].join('');
                logDescription.append(logChange);
            }

            // append components for the changes to the dialog body
            currentLogDiv.append(logDescription);
            gdpChangeLogBody.append(currentLogDiv);
        }

        // continue button that will dismiss the dialog on-click
        var continueButton = $('
CONTINUE
')
            .click(function() {
                $('.gdp-change-log-background').remove();
            })

        // append change log components together and to the DOM body
        gdpChangeLogDialog.append(gdpChangeLogHeader);
        gdpChangeLogDialog.append(gdpChangeLogBody);
        gdpChangeLogDialog.append(continueButton);
        gdpChangeLogBackground.append(gdpChangeLogDialog);
        $('body').append(gdpChangeLogBackground);
    },
    /* Updates to be displayed to the user in the change log dialog */
    updates: [
        {
            "date": "June 1, 2016",
            "description" : [
                "You can now add a custom song from YouTube by providing a normal YouTube url " +
                    "(i.e. https://www.youtube.com/watch?v=npjF032TDDQ) in the 'Add a custom song '" +
                    "URL or YouTube link.' input field when selecting a song. In order to comply with " +
                    "YouTube's terms of service, the video itself will be displayed next to the other " +
                    "menu buttons.",
                "You can now also add a custom gif dancer by providing a normal Giphy url " +
                    "(i.e. http://giphy.com/gifs/texas-taco-tacos-xT4uQulxzV39haRFjG) in the " +
                    "'Add a custom gif url' input field when adding a dancer.",
                "Added the song from http://www.breadfish.co.uk to the song list (BREADFISH).",
                "Added several more gif dancers to the list."
            ]
        },
        {
            "date": "December 13, 2015",
            "description": [
                "Added a new SHAKE button at the bottom of the screen. Try it out!"
            ]
        }]
};
<commit_msg>Updating date in change log entry<commit_after>/**
 * Provides the "WHAT'S NEW WITH GDPE?" dialog displaying GDP changes when the party is
 * started after a new version is released.
 */
gdpChangeLog = {
    /* Display the change log for the user if either there is no cached
       version number in chrome storage or if the cached version doesn't
       match the current GDP extension version in the manifest. Don't
       display the change log if the manifest cannot be fetched */
    checkForUpdates: function() {
        try {
            // fetch manifest to get version number
            var manifest = chrome.runtime.getManifest()
        } catch (e) {
            // supposedly getManifest doesn't work for versions of Chrome less
            // than 22 so don't bother with the versions
            return;
        }

        var currentVersion = manifest.version;

        // fetch the cached version from chrome storage
        chrome.storage.sync.get("cachedVersion", function(storageItem) {
            if (currentVersion !== storageItem['cachedVersion']) {
                // update the cached version to be the current version
                chrome.storage.sync.set({
                    cachedVersion: currentVersion
                });
                // open the "What's New?" change log dialog
                gdpChangeLog.openChangeLog();
            }
        });
    },
    /* Remove the change log dialog if it is open */
    closeChangeLog: function() {
        $('.gdp-change-log-background').remove();
    },
    /* Open the change log dialog on the page. Clicking the CONTINUE
       button or clicking outside the dialog will close the dialog. */
    openChangeLog: function() {
        // transparent full screen div that contains the version changes, but
        // will close on click
        var gdpChangeLogBackground = $('

')
            .css('z-index', maxZIndex)
            .click(function() {
                $(this).remove();
            });

        // actual dialog displaying all of the content
        var gdpChangeLogDialog = $('

')
            .click(function(event) {
                event.stopPropagation();
            });

        // "WHAT'S NEW WITH GDP" dialog header
        var gdpChangeLogHeader = ['
',
                                      '',
                                      "WHAT'S NEW WITH GDPE?",
                                      '',
                                  '
'].join('');

        // body of dialog containing version changes
        var gdpChangeLogBody = $('

');

        // loop through each release version
        for (var i = 0; i < gdpChangeLog.updates.length; i++) {
            var currentLogDiv = $('

');
            var currentLog = gdpChangeLog.updates[i];

            // displays release date of version
            var logDate = ['
',
                               currentLog.date,
                           '
'].join('');
            currentLogDiv.append(logDate);

            // displays all changes for the current version
            var logDescription = $('

');

            // loop through each individual change within the current version
            for (var j = 0; j < currentLog.description.length; j++) {
                var logChange = ['
',
                                     currentLog.description[j],
                                 '
'].join('');
                logDescription.append(logChange);
            }

            // append components for the changes to the dialog body
            currentLogDiv.append(logDescription);
            gdpChangeLogBody.append(currentLogDiv);
        }

        // continue button that will dismiss the dialog on-click
        var continueButton = $('
CONTINUE
')
            .click(function() {
                $('.gdp-change-log-background').remove();
            })

        // append change log components together and to the DOM body
        gdpChangeLogDialog.append(gdpChangeLogHeader);
        gdpChangeLogDialog.append(gdpChangeLogBody);
        gdpChangeLogDialog.append(continueButton);
        gdpChangeLogBackground.append(gdpChangeLogDialog);
        $('body').append(gdpChangeLogBackground);
    },
    /* Updates to be displayed to the user in the change log dialog */
    updates: [
        {
            "date": "June 2, 2016",
            "description" : [
                "You can now add a custom song from YouTube by providing a normal YouTube url " +
                    "(i.e. https://www.youtube.com/watch?v=npjF032TDDQ) in the 'Add a custom song '" +
                    "URL or YouTube link.' input field when selecting a song. In order to comply with " +
                    "YouTube's terms of service, the video itself will be displayed next to the other " +
                    "menu buttons.",
                "You can now also add a custom gif dancer by providing a normal Giphy url " +
                    "(i.e. http://giphy.com/gifs/texas-taco-tacos-xT4uQulxzV39haRFjG) in the " +
                    "'Add a custom gif url' input field when adding a dancer.",
                "Added the song from http://www.breadfish.co.uk to the song list (BREADFISH).",
                "Added several more gif dancers to the list."
            ]
        },
        {
            "date": "December 13, 2015",
            "description": [
                "Added a new SHAKE button at the bottom of the screen. Try it out!"
            ]
        }]
};
<|endoftext|>


<commit_before>'use strict';
const gulp = require('gulp'),
    gutil = require('gulp-util'),
    gulpJsdoc2md = require('gulp-jsdoc-to-markdown'),
    rename = require('gulp-rename'),
    istanbul = require('gulp-istanbul'),
    istanbulHarmony = require('istanbul-harmony'),
    mocha = require('gulp-mocha'),
    eslint = require('gulp-eslint'),
    git = require('gulp-git'),
    concat = require('gulp-concat');

const sockFiles = ['*.js', '!./gulpfile.js', 'plugins/**/*.js'],
    sockDocs = ['README.md', 'docs/**/*.md'],
    sockTests = ['test/**/*.js'],
    sockReadme = ['docs/badges.md.tmpl', 'docs/index.md', 'docs/Special Thanks.md'];

const JobNumber = process.env.TRAVIS_JOB_NUMBER,
    runDocs = /[.]1$/.test(JobNumber||''),
    logger = gutil.log;

console.log(JobNumber);
console.log(runDocs);
/**
 * Generate README.md.
 *
 * Generate document by concatenating badges.md.tmpl, index.md, and special Thanks./md
 */
gulp.task('readme', () => {
    gulp.src(sockReadme)
        .pipe(concat('README.md'))
        .pipe(gulp.dest('.'));
});

gulp.task('docs', ['gitBranch'], function (done) {
    if (!runDocs) {
        return done();
    }
    gulp.src(sockFiles)
        .pipe(gulpJsdoc2md({}))
        .on('error', done)
        .pipe(rename((path) => {
            path.extname = '.md';
        }))
        .pipe(gulp.dest('docs/api'))
        .on('finish', done);
});

gulp.task('lint', (done) => {
    gulp.src(sockFiles)
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError())
        .on('error', done);
});

gulp.task('gitConfig', (done) => {
    if (!JobNumber) {
        return done();
    }
    git.exec({
        args: 'config user.name "Travis-CI"'
    }, () => {
        git.exec({
            args: 'config user.email "Travis-CI@servercooties.com"'
        }, () => {
            done();
        });
    });
});

gulp.task('gitBranch', (done) => {
    let complete = false;
    if (!runDocs) {
        return done();
    }
    const branch = process.env.TRAVIS_BRANCH;
    if (!branch) {
        return done();
    }
    git.checkout(branch, () => {
        git.pull('origin', branch, () => {
            complete || done();
            complete = true;
        });
    });
});

gulp.task('commitDocs', (done) => {
    gulp.src(sockDocs)
        .pipe(git.add())
        .pipe(git.commit('Automatically push updated documentation [ci skip]'))
        .on('error', () => 0)
        .on('finish', done);
});
gulp.task('pushDocs', ['gitConfig', 'commitDocs'], (done) => {
    if (!runDocs) {
        return done();
    }
    const username = process.env.GITHUB_USERNAME,
        token = process.env.GITHUB_TOKEN;
    //suppress output because sensitive things could get leaked
    gutil.log = () => 0;
    git.addRemote('github', 'https://' + username + ':' + token +
        '@github.com/SockDrawer/SockBot.git', (e) => {
            if (e) {
                console.log(e); //eslint-disable-line no-console
            }
            git.push('github', 'es6-dev', {
                args: ['-q']
            }, () => {
                //restore logging for the rest of the build
                gutil.log = logger;
                done();
            });
        });
});

gulp.task('test', (done) => {
    gulp.src(sockFiles).pipe(istanbul({
            instrumenter: istanbulHarmony.Instrumenter
        }))
        .pipe(istanbul.hookRequire())
        .on('finish', () => {
            gulp.src(sockTests)
                .pipe(mocha())
                .pipe(istanbul.writeReports())
                .on('finish', done);
        });
});

gulp.task('buildDocs', ['readme', 'docs'], () => 0);
gulp.task('preBuild', ['buildDocs', 'lint'], () => 0);
gulp.task('postBuild', ['pushDocs'], () => 0);
gulp.task('default', ['lint'], () => 0);<commit_msg>document gulpfile<commit_after>'use strict';
const gulp = require('gulp'),
    gutil = require('gulp-util'),
    gulpJsdoc2md = require('gulp-jsdoc-to-markdown'),
    rename = require('gulp-rename'),
    istanbul = require('gulp-istanbul'),
    istanbulHarmony = require('istanbul-harmony'),
    mocha = require('gulp-mocha'),
    eslint = require('gulp-eslint'),
    git = require('gulp-git'),
    concat = require('gulp-concat');

const sockFiles = ['*.js', '!./gulpfile.js', 'plugins/**/*.js'],
    sockDocs = ['README.md', 'docs/**/*.md'],
    sockTests = ['test/**/*.js'],
    sockReadme = ['docs/badges.md.tmpl', 'docs/index.md', 'docs/Special Thanks.md'];

const JobNumber = process.env.TRAVIS_JOB_NUMBER,
    runDocs = !JobNumber || /[.]1$/.test(JobNumber),
    logger = gutil.log;

/**
 * Generate README.md.
 *
 * Generate document by concatenating badges.md.tmpl, index.md, and Special Thanks.md from docs/
 */
gulp.task('readme', (done) => {
    // Abort(successfully) early if running in CI and not job #1
    if (!runDocs) {
        return done();
    }
    gulp.src(sockReadme)
        .pipe(concat('README.md'))
        .pipe(gulp.dest('.'))
        .on('finish', done);
});

/**
 * Generate API documentation for all js files, place markup in the correct folder for readthedocs.org
 */
gulp.task('docs', ['gitBranch'], function (done) {
    // Abort(successfully) early if running in CI and not job #1
    if (!runDocs) {
        return done();
    }
    gulp.src(sockFiles)
        .pipe(gulpJsdoc2md({}))
        .on('error', done)
        .pipe(rename((path) => {
            path.extname = '.md';
        }))
        .pipe(gulp.dest('docs/api'))
        .on('finish', done);
});

/**
 * Run all js fules through eslint and report status.
 */
gulp.task('lint', (done) => {
    gulp.src(sockFiles)
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError())
        .on('error', done);
});

/**
 * Set git username/email to CI user
 */
gulp.task('gitConfig', (done) => {
    // Abort(successfully) early if not running in CI
    if (!JobNumber) {
        return done();
    }
    git.exec({
        args: 'config user.name "Travis-CI"'
    }, () => {
        git.exec({
            args: 'config user.email "Travis-CI@servercooties.com"'
        }, () => {
            done();
        });
    });
});

/**
 * Pull git branch locally (solves detached head issue in CI)
 */
gulp.task('gitBranch', (done) => {
    // Abort(successfully) early if not running in CI
    if (!JobNumber) {
        return done();
    }
    let complete = false;
    if (!runDocs) {
        return done();
    }
    const branch = process.env.TRAVIS_BRANCH;
    if (!branch) {
        return done();
    }
    git.checkout(branch, () => {
        git.pull('origin', branch, () => {
            if (!complete) {
                done();
            }
            complete = true;
        });
    });
});

/**
 * Commit generated documentation to be picked up by readthedocs.org
 *
 * Add CI tag to commit to prevent CI jobs from being created by checking in docs
 */
gulp.task('commitDocs', (done) => {
    gulp.src(sockDocs)
        .pipe(git.add())
        .pipe(git.commit('Automatically push updated documentation [ci skip]'))
        .on('error', () => 0)
        .on('finish', done);
});

/**
 * Commit and push docs to github to be picked up by readthedocs.org
 */
gulp.task('pushDocs', ['gitConfig', 'commitDocs'], (done) => {
    //Abort(successfully) early if running in CI and not job #1
    if (!runDocs) {
        return done();
    }
    const username = process.env.GITHUB_USERNAME,
        token = process.env.GITHUB_TOKEN;
    // suppress output because sensitive things could get leaked
    // this could suppress other logging from parallel tasks.
    // that risk is deemed acceptable to prevent sensitive information leaking
    gutil.log = () => 0;
    git.addRemote('github', 'https://' + username + ':' + token +
        '@github.com/SockDrawer/SockBot.git', (e) => {
            if (e) {
                gutil.log = logger;
                return done();
            } else {
                git.push('github', 'es6-dev', {
                    args: ['-q']
                }, () => {
                    //restore logging for the rest of the build
                    gutil.log = logger;
                    done();
                });
            }
        });
});

/**
 * Run code coverage instrumented tests
 */
gulp.task('test', (done) => {
    gulp.src(sockFiles)
        // Instrument code files with istanbulHarmony
        .pipe(istanbul({
            instrumenter: istanbulHarmony.Instrumenter
        }))
        // hook require function for complete code coverage
        .pipe(istanbul.hookRequire())
        .on('finish', () => {
            // Run all tests
            gulp.src(sockTests)
                .pipe(mocha())
                // Write code coverage reports
                .pipe(istanbul.writeReports())
                .on('finish', done);
        });
});

// Meta tasks
gulp.task('buildDocs', ['readme', 'docs'], () => 0);
gulp.task('preBuild', ['buildDocs', 'lint'], () => 0);
gulp.task('postBuild', ['pushDocs'], () => 0);
gulp.task('default', ['lint'], () => 0);
<|endoftext|>


<commit_before>module.exports = function(assetsDir, publicDir) {
	if (typeof assetsDir !== 'string' || !assetsDir.length)
	{
		assetsDir = 'resources/assets/';
	}

	if (typeof publicDir !== 'string' || !publicDir.length)
	{
		publicDir = 'public/';
	}

	if (assetsDir.slice(-1) !== '/')
	{
		assetsDir += '/';
	}

	if (publicDir.slice(-1) !== '/')
	{
		publicDir += '/';
	}

	return {
		assetsDir: assetsDir,
		publicDir: publicDir,
		tmpDir: assetsDir + 'tmp',

		css: {
			src: assetsDir + 'css/*.css',
			dest: publicDir + 'css',
			deploy: publicDir + 'css/*.css',
			autoprefixer: {
				browsers: ['> 1%', 'last 2 version', 'ie 8', 'ie 9', 'Opera 12.1']
			}
		},

		sass: {
			src: assetsDir + 'scss/**/*.scss',
			dest: assetsDir + 'css'
		},

		js: {
			modules: {
				vendor: assetsDir + 'js/vendor/**/*.js',
				main: assetsDir + 'js/main.js'
			},
			dest: publicDir + 'js',
			deploy: publicDir + 'js/*.js'
		},

		images: {
			src: assetsDir + 'images/**/*',
			dest: publicDir + 'images'
		},

		fonts: {
			src: assetsDir + 'fonts/**/*',
			dest: publicDir + 'fonts'
		},

		sprites: {
			src: assetsDir + 'sprites/**/*.png',
			dest: publicDir + 'spritesheets',

			sheet: 'sprites.png',

			sheetPath: '../spritesheets/sprites.png',
			partial: '_sprites.scss',
			partialDir: assetsDir + 'scss/partials',

			prefix: 'sprite_',
			algorithm: 'binary-tree',
			padding: 2
		},

		svgSprites: {
			src: assetsDir + 'svg-sprites/**/*.svg',
			dest: publicDir + 'svg-spritesheets',

			sheet: 'sprites.svg',

			partialDir: assetsDir + 'scss/partials',
			partialTemplate: '_svg-sprites-template.scss',
			partial: '_svg-sprites.scss',

			separator: '_',
			padding: 2,
		}
	};
};
<commit_msg>Updating the default config to get rid of IE8, IE9, and Opera.<commit_after>module.exports = function(assetsDir, publicDir) {
	if (typeof assetsDir !== 'string' || !assetsDir.length)
	{
		assetsDir = 'resources/assets/';
	}

	if (typeof publicDir !== 'string' || !publicDir.length)
	{
		publicDir = 'public/';
	}

	if (assetsDir.slice(-1) !== '/')
	{
		assetsDir += '/';
	}

	if (publicDir.slice(-1) !== '/')
	{
		publicDir += '/';
	}

	return {
		assetsDir: assetsDir,
		publicDir: publicDir,
		tmpDir: assetsDir + 'tmp',

		css: {
			src: assetsDir + 'css/*.css',
			dest: publicDir + 'css',
			deploy: publicDir + 'css/*.css',
			autoprefixer: {
				browsers: ['> 1%', 'last 2 version']
			}
		},

		sass: {
			src: assetsDir + 'scss/**/*.scss',
			dest: assetsDir + 'css'
		},

		js: {
			modules: {
				vendor: assetsDir + 'js/vendor/**/*.js',
				main: assetsDir + 'js/main.js'
			},
			dest: publicDir + 'js',
			deploy: publicDir + 'js/*.js'
		},

		images: {
			src: assetsDir + 'images/**/*',
			dest: publicDir + 'images'
		},

		fonts: {
			src: assetsDir + 'fonts/**/*',
			dest: publicDir + 'fonts'
		},

		sprites: {
			src: assetsDir + 'sprites/**/*.png',
			dest: publicDir + 'spritesheets',

			sheet: 'sprites.png',

			sheetPath: '../spritesheets/sprites.png',
			partial: '_sprites.scss',
			partialDir: assetsDir + 'scss/partials',

			prefix: 'sprite_',
			algorithm: 'binary-tree',
			padding: 2
		},

		svgSprites: {
			src: assetsDir + 'svg-sprites/**/*.svg',
			dest: publicDir + 'svg-spritesheets',

			sheet: 'sprites.svg',

			partialDir: assetsDir + 'scss/partials',
			partialTemplate: '_svg-sprites-template.scss',
			partial: '_svg-sprites.scss',

			separator: '_',
			padding: 2,
		}
	};
};
<|endoftext|>


<commit_before>/**
 * @file Exports middleware for creating and attaching a Handlebars renderer
 *       to a Koa2 application context.
 */

const fs         = require('fs');
const Handlebars = require('handlebars');
const path       = require('path');

const templateManager = require('./template-manager.js');

/**
 * Creates Handlebars renderer middleware for Koa2 applications.
 * @param  {Object} options Configuration options
 * @return {AsyncFunction}
 * @throws {ReferenceError}
 */
module.exports = function createRenderer(options) {
  let opts = Object.assign({
    cacheExpires:     60,
    contentTag:       'content',
    defaultLayout:    'default',
    environment:       process.env.NODE_ENV,
    extension:        '.hbs',
    hbs:              Handlebars.create(),
    helperNamespaces: false,
    Promise:          Promise
  }, options);

  // Throw errors if required options aren't specified
  if (!opts.paths) {
    throw new ReferenceError('options.paths is required');
  } else if (!opts.paths.views) {
    throw new ReferenceError('options.paths.views is required');
  }

  // Normalize the extension. Should be `.ext` and not `ext` alone
  const ext = opts.extension = `.${opts.extension.replace(/[^a-z-]/, '')}`;

  // The empty layout should be based on the contentTag option
  const emptyLayout = Handlebars.compile(`{{{${opts.contentTag}}}}`);

  // One-time operation: preload all helpers
  if (opts.paths.helpers) {
    fs.readdirSync(opts.paths.helpers).filter(file => {
      return path.extname(file) === '.js';
    }).forEach(helperFile => {
      let helperPath = path.join(opts.paths.helpers, helperFile),
          name       = path.basename(helperFile, '.js');

      const helperData = require(helperPath);
      if (typeof helperData === 'function') {
        opts.hbs.registerHelper(name, require(helperPath));
      } else if (typeof helperData === 'object') {
        // support for object literals containing multiple files (and namespaces by file name)
        Object.keys(helperData).forEach(key => {
          if (helperData[key] !== undefined) {
            opts.hbs.registerHelper(
              opts.helperNamespaces
                ? `${name}_${key}`
                : key, helperData[Key]);
          }
        });
      } else throw new Error(`helper file: ${name} contains invalid type`);
    });
  }

  // A template manager object to handle compiling, caching, etc.
  let manager = templateManager(opts);

  /**
   * Renders the specified view using the specified layout, partials, and
   * context into a string.
   *
   * Although another Handlebars environment may be provided to the underlying
   * renderer middleware, and partials manually registered with that environment
   * will be used when rendering templates, all partials loaded by this module
   * are handled manually by this module. There is no significant performance or
   * usability advantage to using the #registerPartial method included in the
   * Handlebars API to register partials instead; please see the Handlebars.js
   * source code for further details.
   * @param  {Function}          view     The view to render
   * @param  {Function}          layout   The layout to render the view onto
   * @param  {Object.<Function>} partials An object map of partials
   * @param  {Object}            context  Data to be rendered into the view
   * @return {String}
   * @private
   * @see {@link https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/base.js#L49}
   */
  function renderView(view, layout, partials, context) {
    let options = {
      partials: partials
    };

    // Handle dynamically-defined content tag
    let renderedView = {};
    renderedView[opts.contentTag] = view(context, options);

    return layout(Object.assign({}, context, renderedView), options);
  };

  /**
   * Attaches #render function to a specified Koa context object.
   * @param  {Object}   ctx  A Koa context object
   * @param  {Function} next The next Koa middleware in the chain
   */
  return async function rendererMiddleware(ctx, next) {

    /**
     * Renders the specified view.
     * @param  {String} viewName The name of the view to render
     * @param  {Object} [locals] Local data passed to be rendered
     * @public
     */
    ctx.render = async function render(viewName, locals) {
      let context  = Object.assign({}, ctx.state, locals),
          layout   = emptyLayout,
          partials = {},
          viewPath = path.resolve(opts.paths.views, `${viewName}${ext}`),
          view     = await manager.compileTemplate(viewPath, 'view');

      // If a layouts path is specified, retrieve the layout
      if (opts.paths.layouts) {
        let layoutName = context.layout || opts.defaultLayout,
            layoutPath = path.resolve(opts.paths.layouts, `${layoutName}${ext}`);

        layout = await manager.compileTemplate(layoutPath, 'layout');
      }

      // If a partials path is specified, retrieve the partials
      if (opts.paths.partials) {
        partials = await manager.compileTemplates(opts.paths.partials, 'partial');
      }

      ctx.type = 'text/html; charset=utf-8';
      ctx.body = renderView(view, layout, partials, context);
    };

    await next();
  };
};
<commit_msg>minor bug fix to support for helpers with object literals<commit_after>/**
 * @file Exports middleware for creating and attaching a Handlebars renderer
 *       to a Koa2 application context.
 */

const fs         = require('fs');
const Handlebars = require('handlebars');
const path       = require('path');

const templateManager = require('./template-manager.js');

/**
 * Creates Handlebars renderer middleware for Koa2 applications.
 * @param  {Object} options Configuration options
 * @return {AsyncFunction}
 * @throws {ReferenceError}
 */
module.exports = function createRenderer(options) {
  let opts = Object.assign({
    cacheExpires:     60,
    contentTag:       'content',
    defaultLayout:    'default',
    environment:       process.env.NODE_ENV,
    extension:        '.hbs',
    hbs:              Handlebars.create(),
    helperNamespaces: false,
    Promise:          Promise
  }, options);

  // Throw errors if required options aren't specified
  if (!opts.paths) {
    throw new ReferenceError('options.paths is required');
  } else if (!opts.paths.views) {
    throw new ReferenceError('options.paths.views is required');
  }

  // Normalize the extension. Should be `.ext` and not `ext` alone
  const ext = opts.extension = `.${opts.extension.replace(/[^a-z-]/, '')}`;

  // The empty layout should be based on the contentTag option
  const emptyLayout = Handlebars.compile(`{{{${opts.contentTag}}}}`);

  // One-time operation: preload all helpers
  if (opts.paths.helpers) {
    fs.readdirSync(opts.paths.helpers).filter(file => {
      return path.extname(file) === '.js';
    }).forEach(helperFile => {
      let helperPath = path.join(opts.paths.helpers, helperFile),
          name       = path.basename(helperFile, '.js');

      const helperData = require(helperPath);
      if (typeof helperData === 'function') {
        opts.hbs.registerHelper(name, require(helperPath));
      } else if (typeof helperData === 'object') {
        // support for object literals containing multiple files (and namespaces by file name)
        Object.keys(helperData).forEach((key) => {
          if (helperData[key] !== undefined) {
            opts.hbs.registerHelper(
              opts.helperNamespaces
                ? `${name}_${key}`
                : key, helperData[key]);
          }
        });
      } else throw new Error(`helper file: ${name} contains invalid type`);
    });
  }

  // A template manager object to handle compiling, caching, etc.
  let manager = templateManager(opts);

  /**
   * Renders the specified view using the specified layout, partials, and
   * context into a string.
   *
   * Although another Handlebars environment may be provided to the underlying
   * renderer middleware, and partials manually registered with that environment
   * will be used when rendering templates, all partials loaded by this module
   * are handled manually by this module. There is no significant performance or
   * usability advantage to using the #registerPartial method included in the
   * Handlebars API to register partials instead; please see the Handlebars.js
   * source code for further details.
   * @param  {Function}          view     The view to render
   * @param  {Function}          layout   The layout to render the view onto
   * @param  {Object.<Function>} partials An object map of partials
   * @param  {Object}            context  Data to be rendered into the view
   * @return {String}
   * @private
   * @see {@link https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/base.js#L49}
   */
  function renderView(view, layout, partials, context) {
    let options = {
      partials: partials
    };

    // Handle dynamically-defined content tag
    let renderedView = {};
    renderedView[opts.contentTag] = view(context, options);

    return layout(Object.assign({}, context, renderedView), options);
  };

  /**
   * Attaches #render function to a specified Koa context object.
   * @param  {Object}   ctx  A Koa context object
   * @param  {Function} next The next Koa middleware in the chain
   */
  return async function rendererMiddleware(ctx, next) {

    /**
     * Renders the specified view.
     * @param  {String} viewName The name of the view to render
     * @param  {Object} [locals] Local data passed to be rendered
     * @public
     */
    ctx.render = async function render(viewName, locals) {
      let context  = Object.assign({}, ctx.state, locals),
          layout   = emptyLayout,
          partials = {},
          viewPath = path.resolve(opts.paths.views, `${viewName}${ext}`),
          view     = await manager.compileTemplate(viewPath, 'view');

      // If a layouts path is specified, retrieve the layout
      if (opts.paths.layouts) {
        let layoutName = context.layout || opts.defaultLayout,
            layoutPath = path.resolve(opts.paths.layouts, `${layoutName}${ext}`);

        layout = await manager.compileTemplate(layoutPath, 'layout');
      }

      // If a partials path is specified, retrieve the partials
      if (opts.paths.partials) {
        partials = await manager.compileTemplates(opts.paths.partials, 'partial');
      }

      ctx.type = 'text/html; charset=utf-8';
      ctx.body = renderView(view, layout, partials, context);
    };

    await next();
  };
};
<|endoftext|>