<commit_before>iD.svg.Labels = function(projection, context) {

    // Replace with dict and iterate over entities tags instead?
    var label_stack = [
        ['line', 'aeroway'],
        ['line', 'highway'],
        ['line', 'railway'],
        ['line', 'waterway'],
        ['area', 'aeroway'],
        ['area', 'amenity'],
        ['area', 'building'],
        ['area', 'historic'],
        ['area', 'leisure'],
        ['area', 'man_made'],
        ['area', 'natural'],
        ['area', 'shop'],
        ['area', 'tourism'],
        ['point', 'aeroway'],
        ['point', 'amenity'],
        ['point', 'building'],
        ['point', 'historic'],
        ['point', 'leisure'],
        ['point', 'man_made'],
        ['point', 'natural'],
        ['point', 'shop'],
        ['point', 'tourism'],
        ['line', 'name'],
        ['area', 'name'],
        ['point', 'name']
    ];

    var default_size = 12;

    var font_sizes = label_stack.map(function(d) {
        var style = iD.util.getStyle('text.' + d[0] + '.tag-' + d[1]),
            m = style && style.cssText.match("font-size: ([0-9]{1,2})px;");
        if (m) return parseInt(m[1], 10);

        style = iD.util.getStyle('text.' + d[0]);
        m = style && style.cssText.match("font-size: ([0-9]{1,2})px;");
        if (m) return parseInt(m[1], 10);

        return default_size;
    });

    var iconSize = 18;

    var pointOffsets = [
        [15, -11, 'start'], // right
        [10, -11, 'start'], // unused right now
        [-15, -11, 'end']
    ];

    var lineOffsets = [50, 45, 55, 40, 60, 35, 65, 30, 70, 25,
        75, 20, 80, 15, 95, 10, 90, 5, 95];

    var noIcons = ['building', 'landuse', 'natural'];
    function blacklisted(preset) {
        return _.any(noIcons, function(s) {
            return preset.id.indexOf(s) >= 0;
        });
    }

    function get(array, prop) {
        return function(d, i) { return array[i][prop]; };
    }

    var textWidthCache = {};

    function textWidth(text, size, elem) {
        var c = textWidthCache[size];
        if (!c) c = textWidthCache[size] = {};

        if (c[text]) {
            return c[text];

        } else if (elem) {
            c[text] = elem.getComputedTextLength();
            return c[text];

        } else {
            var str = encodeURIComponent(text).match(/%[CDEFcdef]/g);
            if (str == null) {
                return size / 3 * 2 * text.length;
            } else {
                return size / 3 * ( 2 * text.length + str.length);
            }
        }
    }

    function drawLineLabels(group, entities, filter, classes, labels) {

        var texts = group.selectAll('text.' + classes)
            .filter(filter)
            .data(entities, iD.Entity.key);

        var tp = texts.enter()
            .append('text')
            .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; })
            .append('textPath')
            .attr('class', 'textpath');

        var tps = texts.selectAll('.textpath')
            .filter(filter)
            .data(entities, iD.Entity.key)
            .attr({
                'startOffset': '50%',
                'xlink:href': function(d) { return '#labelpath-' + d.id; }
            })
            .text(iD.util.displayName);

        texts.exit().remove();

    }

    function drawLinePaths(group, entities, filter, classes, labels) {

        var halos = group.selectAll('path')
            .filter(filter)
            .data(entities, iD.Entity.key);

        halos.enter()
            .append('path')
            .style('stroke-width', get(labels, 'font-size'))
            .attr('id', function(d) { return 'labelpath-' + d.id; })
            .attr('class', classes);

        halos.attr('d', get(labels, 'lineString'));

        halos.exit().remove();
    }

    function drawPointLabels(group, entities, filter, classes, labels) {

        var texts = group.selectAll('text.' + classes)
            .filter(filter)
            .data(entities, iD.Entity.key);

        texts.enter()
            .append('text')
            .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; });

        texts.attr('x', get(labels, 'x'))
            .attr('y', get(labels, 'y'))
            .style('text-anchor', get(labels, 'textAnchor'))
            .text(iD.util.displayName)
            .each(function(d, i) { textWidth(iD.util.displayName(d), labels[i].height, this); });

        texts.exit().remove();
        return texts;
    }

    function drawAreaLabels(group, entities, filter, classes, labels) {
        entities = entities.filter(hasText);
        labels = labels.filter(hasText);
        return drawPointLabels(group, entities, filter, classes, labels);

        function hasText(d, i) {
            return labels[i].hasOwnProperty('x') && labels[i].hasOwnProperty('y');
        }
    }

    function drawAreaIcons(group, entities, filter, classes, labels) {

        var icons = group.selectAll('use')
            .filter(filter)
            .data(entities, iD.Entity.key);

        icons.enter()
            .append('use')
            .attr('clip-path', 'url(#clip-square-18)')
            .attr('class', 'icon');

        icons.attr('transform', get(labels, 'transform'))
            .attr('xlink:href', function(d) {
                return '#maki-' + context.presets().match(d, context.graph()).icon + '-18';
            });

        icons.exit().remove();
    }

    function reverse(p) {
        var angle = Math.atan2(p[1][1] - p[0][1], p[1][0] - p[0][0]);
        return !(p[0][0] < p[p.length - 1][0] && angle < Math.PI/2 && angle > - Math.PI/2);
    }

    function lineString(nodes) {
        return 'M' + nodes.join('L');
    }

    function subpath(nodes, from, to) {
        function segmentLength(i) {
            var dx = nodes[i][0] - nodes[i + 1][0];
            var dy = nodes[i][1] - nodes[i + 1][1];
            return Math.sqrt(dx * dx + dy * dy);
        }

        var sofar = 0,
            start, end, i0, i1;
        for (var i = 0; i < nodes.length - 1; i++) {
            var current = segmentLength(i);
            var portion;
            if (!start && sofar + current >= from) {
                portion = (from - sofar) / current;
                start = [
                    nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
                    nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1])
                ];
                i0 = i + 1;
            }
            if (!end && sofar + current >= to) {
                portion = (to - sofar) / current;
                end = [
                    nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
                    nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1])
                ];
                i1 = i + 1;
            }
            sofar += current;

        }
        var ret = nodes.slice(i0, i1);
        ret.unshift(start);
        ret.push(end);
        return ret;

    }

    function hideOnMouseover() {
        var layers = d3.select(this)
            .selectAll('.layer-label, .layer-halo');

        layers.selectAll('.proximate')
            .classed('proximate', false);

        var mouse = context.mouse(),
            pad = 50,
            rect = [mouse[0] - pad, mouse[1] - pad, mouse[0] + pad, mouse[1] + pad],
            ids = _.pluck(rtree.search(rect), 'id');

        if (!ids.length) return;
        layers.selectAll('.' + ids.join(', .'))
            .classed('proximate', true);
    }

    var rtree = rbush(),
        rectangles = {};

    function labels(surface, graph, entities, filter, dimensions, fullRedraw) {

        var hidePoints = !surface.select('.node.point').node();

        var labelable = [], i, k, entity;
        for (i = 0; i < label_stack.length; i++) labelable.push([]);

        if (fullRedraw) {
            rtree.clear();
            rectangles = {};
        } else {
            for (i = 0; i < entities.length; i++) {
                rtree.remove(rectangles[entities[i].id]);
            }
        }

        // Split entities into groups specified by label_stack
        for (i = 0; i < entities.length; i++) {
            entity = entities[i];
            var geometry = entity.geometry(graph);

            if (geometry === 'vertex')
                continue;
            if (hidePoints && geometry === 'point')
                continue;

            var preset = geometry === 'area' && context.presets().match(entity, graph),
                icon = preset && !blacklisted(preset) && preset.icon;

            if (!icon && !iD.util.displayName(entity))
                continue;

            for (k = 0; k < label_stack.length; k ++) {
                if (geometry === label_stack[k][0] && entity.tags[label_stack[k][1]]) {
                    labelable[k].push(entity);
                    break;
                }
            }
        }

        var positions = {
            point: [],
            line: [],
            area: []
        };

        var labelled = {
            point: [],
            line: [],
            area: []
        };

        // Try and find a valid label for labellable entities
        for (k = 0; k < labelable.length; k++) {
            var font_size = font_sizes[k];
            for (i = 0; i < labelable[k].length; i ++) {
                entity = labelable[k][i];
                var name = iD.util.displayName(entity),
                    width = name && textWidth(name, font_size),
                    p;
                if (entity.geometry(graph) === 'point') {
                    p = getPointLabel(entity, width, font_size);
                } else if (entity.geometry(graph) === 'line') {
                    p = getLineLabel(entity, width, font_size);
                } else if (entity.geometry(graph) === 'area') {
                    p = getAreaLabel(entity, width, font_size);
                }
                if (p) {
                    p.classes = entity.geometry(graph) + ' tag-' + label_stack[k][1];
                    positions[entity.geometry(graph)].push(p);
                    labelled[entity.geometry(graph)].push(entity);
                }
            }
        }

        function getPointLabel(entity, width, height) {
            var coord = projection(entity.loc),
                m = 5,  // margin
                offset = pointOffsets[0],
                p = {
                    height: height,
                    width: width,
                    x: coord[0] + offset[0],
                    y: coord[1] + offset[1],
                    textAnchor: offset[2]
                };
            var rect = [p.x - m, p.y - m, p.x + width + m, p.y + height + m];
            if (tryInsert(rect, entity.id)) return p;
        }

        function getLineLabel(entity, width, height) {
            var nodes = _.pluck(graph.childNodes(entity), 'loc').map(projection),
                length = iD.geo.pathLength(nodes);
            if (length < width + 20) return;

            for (var i = 0; i < lineOffsets.length; i ++) {
                var offset = lineOffsets[i],
                    middle = offset / 100 * length,
                    start = middle - width/2;
                if (start < 0 || start + width > length) continue;
                var sub = subpath(nodes, start, start + width),
                    rev = reverse(sub),
                    rect = [
                        Math.min(sub[0][0], sub[sub.length - 1][0]) - 10,
                        Math.min(sub[0][1], sub[sub.length - 1][1]) - 10,
                        Math.max(sub[0][0], sub[sub.length - 1][0]) + 20,
                        Math.max(sub[0][1], sub[sub.length - 1][1]) + 30
                    ];
                if (rev) sub = sub.reverse();
                if (tryInsert(rect, entity.id)) return {
                    'font-size': height + 2,
                    lineString: lineString(sub),
                    startOffset: offset + '%'
                };
            }
        }

        function getAreaLabel(entity, width, height) {
            var path = d3.geo.path().projection(projection),
                centroid = path.centroid(entity.asGeoJSON(graph, true)),
                extent = entity.extent(graph),
                entitywidth = projection(extent[1])[0] - projection(extent[0])[0],
                rect;

            if (!centroid || entitywidth < 20) return;

            var iconX = centroid[0] - (iconSize/2),
                iconY = centroid[1] - (iconSize/2),
                textOffset = iconSize + 5;

            var p = {
                transform: 'translate(' + iconX + ',' + iconY + ')'
            };

            if (width && entitywidth >= width + 20) {
                p.x = centroid[0];
                p.y = centroid[1] + textOffset;
                p.textAnchor = 'middle';
                p.height = height;
                rect = [p.x - width/2, p.y, p.x + width/2, p.y + height + textOffset];
            } else {
                rect = [iconX, iconY, iconX + iconSize, iconY + iconSize];
            }

            if (tryInsert(rect, entity.id)) return p;

        }

        function tryInsert(rect, id) {
            // Check that label is visible
            if (rect[0] < 0 || rect[1] < 0 || rect[2] > dimensions[0] ||
                rect[3] > dimensions[1]) return false;
            var v = rtree.search(rect).length === 0;
            if (v) {
                rect.id = id;
                rtree.insert(rect);
                rectangles[id] = rect;
            }
            return v;
        }

        var label = surface.select('.layer-label'),
            halo = surface.select('.layer-halo');

        // points
        drawPointLabels(label, labelled.point, filter, 'pointlabel', positions.point);
        drawPointLabels(halo, labelled.point, filter, 'pointlabel-halo', positions.point);

        // lines
        drawLinePaths(halo, labelled.line, filter, '', positions.line);
        drawLineLabels(label, labelled.line, filter, 'linelabel', positions.line);
        drawLineLabels(halo, labelled.line, filter, 'linelabel-halo', positions.line);

        // areas
        drawAreaLabels(label, labelled.area, filter, 'arealabel', positions.area);
        drawAreaLabels(halo, labelled.area, filter, 'arealabel-halo', positions.area);
        drawAreaIcons(label, labelled.area, filter, 'arealabel-icon', positions.area);
    }

    labels.supersurface = function(supersurface) {
        supersurface
            .on('mousemove.hidelabels', hideOnMouseover)
            .on('mousedown.hidelabels', function () {
                supersurface.on('mousemove.hidelabels', null);
            })
            .on('mouseup.hidelabels', function () {
                supersurface.on('mousemove.hidelabels', hideOnMouseover);
            });
    };

    return labels;
};
<commit_msg>Cleanup style and null check<commit_after>iD.svg.Labels = function(projection, context) {

    // Replace with dict and iterate over entities tags instead?
    var label_stack = [
        ['line', 'aeroway'],
        ['line', 'highway'],
        ['line', 'railway'],
        ['line', 'waterway'],
        ['area', 'aeroway'],
        ['area', 'amenity'],
        ['area', 'building'],
        ['area', 'historic'],
        ['area', 'leisure'],
        ['area', 'man_made'],
        ['area', 'natural'],
        ['area', 'shop'],
        ['area', 'tourism'],
        ['point', 'aeroway'],
        ['point', 'amenity'],
        ['point', 'building'],
        ['point', 'historic'],
        ['point', 'leisure'],
        ['point', 'man_made'],
        ['point', 'natural'],
        ['point', 'shop'],
        ['point', 'tourism'],
        ['line', 'name'],
        ['area', 'name'],
        ['point', 'name']
    ];

    var default_size = 12;

    var font_sizes = label_stack.map(function(d) {
        var style = iD.util.getStyle('text.' + d[0] + '.tag-' + d[1]),
            m = style && style.cssText.match("font-size: ([0-9]{1,2})px;");
        if (m) return parseInt(m[1], 10);

        style = iD.util.getStyle('text.' + d[0]);
        m = style && style.cssText.match("font-size: ([0-9]{1,2})px;");
        if (m) return parseInt(m[1], 10);

        return default_size;
    });

    var iconSize = 18;

    var pointOffsets = [
        [15, -11, 'start'], // right
        [10, -11, 'start'], // unused right now
        [-15, -11, 'end']
    ];

    var lineOffsets = [50, 45, 55, 40, 60, 35, 65, 30, 70, 25,
        75, 20, 80, 15, 95, 10, 90, 5, 95];

    var noIcons = ['building', 'landuse', 'natural'];
    function blacklisted(preset) {
        return _.any(noIcons, function(s) {
            return preset.id.indexOf(s) >= 0;
        });
    }

    function get(array, prop) {
        return function(d, i) { return array[i][prop]; };
    }

    var textWidthCache = {};

    function textWidth(text, size, elem) {
        var c = textWidthCache[size];
        if (!c) c = textWidthCache[size] = {};

        if (c[text]) {
            return c[text];

        } else if (elem) {
            c[text] = elem.getComputedTextLength();
            return c[text];

        } else {
            var str = encodeURIComponent(text).match(/%[CDEFcdef]/g);
            if (str === null) {
                return size / 3 * 2 * text.length;
            } else {
                return size / 3 * (2 * text.length + str.length);
            }
        }
    }

    function drawLineLabels(group, entities, filter, classes, labels) {

        var texts = group.selectAll('text.' + classes)
            .filter(filter)
            .data(entities, iD.Entity.key);

        var tp = texts.enter()
            .append('text')
            .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; })
            .append('textPath')
            .attr('class', 'textpath');

        var tps = texts.selectAll('.textpath')
            .filter(filter)
            .data(entities, iD.Entity.key)
            .attr({
                'startOffset': '50%',
                'xlink:href': function(d) { return '#labelpath-' + d.id; }
            })
            .text(iD.util.displayName);

        texts.exit().remove();

    }

    function drawLinePaths(group, entities, filter, classes, labels) {

        var halos = group.selectAll('path')
            .filter(filter)
            .data(entities, iD.Entity.key);

        halos.enter()
            .append('path')
            .style('stroke-width', get(labels, 'font-size'))
            .attr('id', function(d) { return 'labelpath-' + d.id; })
            .attr('class', classes);

        halos.attr('d', get(labels, 'lineString'));

        halos.exit().remove();
    }

    function drawPointLabels(group, entities, filter, classes, labels) {

        var texts = group.selectAll('text.' + classes)
            .filter(filter)
            .data(entities, iD.Entity.key);

        texts.enter()
            .append('text')
            .attr('class', function(d, i) { return classes + ' ' + labels[i].classes + ' ' + d.id; });

        texts.attr('x', get(labels, 'x'))
            .attr('y', get(labels, 'y'))
            .style('text-anchor', get(labels, 'textAnchor'))
            .text(iD.util.displayName)
            .each(function(d, i) { textWidth(iD.util.displayName(d), labels[i].height, this); });

        texts.exit().remove();
        return texts;
    }

    function drawAreaLabels(group, entities, filter, classes, labels) {
        entities = entities.filter(hasText);
        labels = labels.filter(hasText);
        return drawPointLabels(group, entities, filter, classes, labels);

        function hasText(d, i) {
            return labels[i].hasOwnProperty('x') && labels[i].hasOwnProperty('y');
        }
    }

    function drawAreaIcons(group, entities, filter, classes, labels) {

        var icons = group.selectAll('use')
            .filter(filter)
            .data(entities, iD.Entity.key);

        icons.enter()
            .append('use')
            .attr('clip-path', 'url(#clip-square-18)')
            .attr('class', 'icon');

        icons.attr('transform', get(labels, 'transform'))
            .attr('xlink:href', function(d) {
                return '#maki-' + context.presets().match(d, context.graph()).icon + '-18';
            });

        icons.exit().remove();
    }

    function reverse(p) {
        var angle = Math.atan2(p[1][1] - p[0][1], p[1][0] - p[0][0]);
        return !(p[0][0] < p[p.length - 1][0] && angle < Math.PI/2 && angle > - Math.PI/2);
    }

    function lineString(nodes) {
        return 'M' + nodes.join('L');
    }

    function subpath(nodes, from, to) {
        function segmentLength(i) {
            var dx = nodes[i][0] - nodes[i + 1][0];
            var dy = nodes[i][1] - nodes[i + 1][1];
            return Math.sqrt(dx * dx + dy * dy);
        }

        var sofar = 0,
            start, end, i0, i1;
        for (var i = 0; i < nodes.length - 1; i++) {
            var current = segmentLength(i);
            var portion;
            if (!start && sofar + current >= from) {
                portion = (from - sofar) / current;
                start = [
                    nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
                    nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1])
                ];
                i0 = i + 1;
            }
            if (!end && sofar + current >= to) {
                portion = (to - sofar) / current;
                end = [
                    nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
                    nodes[i][1] + portion * (nodes[i + 1][1] - nodes[i][1])
                ];
                i1 = i + 1;
            }
            sofar += current;

        }
        var ret = nodes.slice(i0, i1);
        ret.unshift(start);
        ret.push(end);
        return ret;

    }

    function hideOnMouseover() {
        var layers = d3.select(this)
            .selectAll('.layer-label, .layer-halo');

        layers.selectAll('.proximate')
            .classed('proximate', false);

        var mouse = context.mouse(),
            pad = 50,
            rect = [mouse[0] - pad, mouse[1] - pad, mouse[0] + pad, mouse[1] + pad],
            ids = _.pluck(rtree.search(rect), 'id');

        if (!ids.length) return;
        layers.selectAll('.' + ids.join(', .'))
            .classed('proximate', true);
    }

    var rtree = rbush(),
        rectangles = {};

    function labels(surface, graph, entities, filter, dimensions, fullRedraw) {

        var hidePoints = !surface.select('.node.point').node();

        var labelable = [], i, k, entity;
        for (i = 0; i < label_stack.length; i++) labelable.push([]);

        if (fullRedraw) {
            rtree.clear();
            rectangles = {};
        } else {
            for (i = 0; i < entities.length; i++) {
                rtree.remove(rectangles[entities[i].id]);
            }
        }

        // Split entities into groups specified by label_stack
        for (i = 0; i < entities.length; i++) {
            entity = entities[i];
            var geometry = entity.geometry(graph);

            if (geometry === 'vertex')
                continue;
            if (hidePoints && geometry === 'point')
                continue;

            var preset = geometry === 'area' && context.presets().match(entity, graph),
                icon = preset && !blacklisted(preset) && preset.icon;

            if (!icon && !iD.util.displayName(entity))
                continue;

            for (k = 0; k < label_stack.length; k ++) {
                if (geometry === label_stack[k][0] && entity.tags[label_stack[k][1]]) {
                    labelable[k].push(entity);
                    break;
                }
            }
        }

        var positions = {
            point: [],
            line: [],
            area: []
        };

        var labelled = {
            point: [],
            line: [],
            area: []
        };

        // Try and find a valid label for labellable entities
        for (k = 0; k < labelable.length; k++) {
            var font_size = font_sizes[k];
            for (i = 0; i < labelable[k].length; i ++) {
                entity = labelable[k][i];
                var name = iD.util.displayName(entity),
                    width = name && textWidth(name, font_size),
                    p;
                if (entity.geometry(graph) === 'point') {
                    p = getPointLabel(entity, width, font_size);
                } else if (entity.geometry(graph) === 'line') {
                    p = getLineLabel(entity, width, font_size);
                } else if (entity.geometry(graph) === 'area') {
                    p = getAreaLabel(entity, width, font_size);
                }
                if (p) {
                    p.classes = entity.geometry(graph) + ' tag-' + label_stack[k][1];
                    positions[entity.geometry(graph)].push(p);
                    labelled[entity.geometry(graph)].push(entity);
                }
            }
        }

        function getPointLabel(entity, width, height) {
            var coord = projection(entity.loc),
                m = 5,  // margin
                offset = pointOffsets[0],
                p = {
                    height: height,
                    width: width,
                    x: coord[0] + offset[0],
                    y: coord[1] + offset[1],
                    textAnchor: offset[2]
                };
            var rect = [p.x - m, p.y - m, p.x + width + m, p.y + height + m];
            if (tryInsert(rect, entity.id)) return p;
        }

        function getLineLabel(entity, width, height) {
            var nodes = _.pluck(graph.childNodes(entity), 'loc').map(projection),
                length = iD.geo.pathLength(nodes);
            if (length < width + 20) return;

            for (var i = 0; i < lineOffsets.length; i ++) {
                var offset = lineOffsets[i],
                    middle = offset / 100 * length,
                    start = middle - width/2;
                if (start < 0 || start + width > length) continue;
                var sub = subpath(nodes, start, start + width),
                    rev = reverse(sub),
                    rect = [
                        Math.min(sub[0][0], sub[sub.length - 1][0]) - 10,
                        Math.min(sub[0][1], sub[sub.length - 1][1]) - 10,
                        Math.max(sub[0][0], sub[sub.length - 1][0]) + 20,
                        Math.max(sub[0][1], sub[sub.length - 1][1]) + 30
                    ];
                if (rev) sub = sub.reverse();
                if (tryInsert(rect, entity.id)) return {
                    'font-size': height + 2,
                    lineString: lineString(sub),
                    startOffset: offset + '%'
                };
            }
        }

        function getAreaLabel(entity, width, height) {
            var path = d3.geo.path().projection(projection),
                centroid = path.centroid(entity.asGeoJSON(graph, true)),
                extent = entity.extent(graph),
                entitywidth = projection(extent[1])[0] - projection(extent[0])[0],
                rect;

            if (!centroid || entitywidth < 20) return;

            var iconX = centroid[0] - (iconSize/2),
                iconY = centroid[1] - (iconSize/2),
                textOffset = iconSize + 5;

            var p = {
                transform: 'translate(' + iconX + ',' + iconY + ')'
            };

            if (width && entitywidth >= width + 20) {
                p.x = centroid[0];
                p.y = centroid[1] + textOffset;
                p.textAnchor = 'middle';
                p.height = height;
                rect = [p.x - width/2, p.y, p.x + width/2, p.y + height + textOffset];
            } else {
                rect = [iconX, iconY, iconX + iconSize, iconY + iconSize];
            }

            if (tryInsert(rect, entity.id)) return p;

        }

        function tryInsert(rect, id) {
            // Check that label is visible
            if (rect[0] < 0 || rect[1] < 0 || rect[2] > dimensions[0] ||
                rect[3] > dimensions[1]) return false;
            var v = rtree.search(rect).length === 0;
            if (v) {
                rect.id = id;
                rtree.insert(rect);
                rectangles[id] = rect;
            }
            return v;
        }

        var label = surface.select('.layer-label'),
            halo = surface.select('.layer-halo');

        // points
        drawPointLabels(label, labelled.point, filter, 'pointlabel', positions.point);
        drawPointLabels(halo, labelled.point, filter, 'pointlabel-halo', positions.point);

        // lines
        drawLinePaths(halo, labelled.line, filter, '', positions.line);
        drawLineLabels(label, labelled.line, filter, 'linelabel', positions.line);
        drawLineLabels(halo, labelled.line, filter, 'linelabel-halo', positions.line);

        // areas
        drawAreaLabels(label, labelled.area, filter, 'arealabel', positions.area);
        drawAreaLabels(halo, labelled.area, filter, 'arealabel-halo', positions.area);
        drawAreaIcons(label, labelled.area, filter, 'arealabel-icon', positions.area);
    }

    labels.supersurface = function(supersurface) {
        supersurface
            .on('mousemove.hidelabels', hideOnMouseover)
            .on('mousedown.hidelabels', function () {
                supersurface.on('mousemove.hidelabels', null);
            })
            .on('mouseup.hidelabels', function () {
                supersurface.on('mousemove.hidelabels', hideOnMouseover);
            });
    };

    return labels;
};
<|endoftext|>


<commit_before>Meteor.publish('sensorList', function(org_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Sensors.find()
    ];
  }
})

Meteor.publish('latestReading', function(sensor_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      //       Readings.find({sensor_id: sensor_id}, {sort: {time: -1}, limit:1}),
      Alarms.find({sensor_id: sensor_id, active: true, owner_id: this.userId})
    ];
  }
})

Meteor.publish('singleSensor', function(sensor_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Sensors.find({_id: sensor_id}),
      Alarms.find({sensor_id: sensor_id, active: true, owner_id: this.userId})
    ];
  }
})

Meteor.publish('alarmList', function(sensor_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Sensors.find({_id: sensor_id}),
      Alarms.find({sensor_id: sensor_id, enabled:true, owner_id: this.userId})
    ];
  }
})

Meteor.publish('editAlarm', function(alarm_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Alarms.find({_id: alarm_id, owner_id: this.userId})
    ];
  }
})

Meteor.publish('history', function(sensor_id, timeStart, timeStop){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      //       Readings.find({sensor_id: sensor_id, $and:[{time: {$gte: timeStart}}, {time:{$lte: timeStop}}]}),
      Sensors.find({_id: sensor_id})
    ];
  }
})
<commit_msg>Re-enabled readings<commit_after>Meteor.publish('sensorList', function(org_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Sensors.find()
    ];
  }
})

Meteor.publish('latestReading', function(sensor_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Readings.find({sensor_id: sensor_id}, {sort: {time: -1}, limit:1}),
      Alarms.find({sensor_id: sensor_id, active: true, owner_id: this.userId})
    ];
  }
})

Meteor.publish('singleSensor', function(sensor_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Sensors.find({_id: sensor_id}),
      Alarms.find({sensor_id: sensor_id, active: true, owner_id: this.userId})
    ];
  }
})

Meteor.publish('alarmList', function(sensor_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Sensors.find({_id: sensor_id}),
      Alarms.find({sensor_id: sensor_id, enabled:true, owner_id: this.userId})
    ];
  }
})

Meteor.publish('editAlarm', function(alarm_id){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Alarms.find({_id: alarm_id, owner_id: this.userId})
    ];
  }
})

Meteor.publish('history', function(sensor_id, timeStart, timeStop){
  var thisUser = Meteor.users.findOne({_id: this.userId});
  if(thisUser){
    return [
      Readings.find({sensor_id: sensor_id, $and:[{time: {$gte: timeStart}}, {time:{$lte: timeStop}}]}),
      Sensors.find({_id: sensor_id})
    ];
  }
})
<|endoftext|>


<commit_before>iD.ui.Restore = function(context) {
    return function(selection) {
        if (!context.history().lock() || !context.history().restorableChanges())
            return;

        var modal = iD.ui.modal(selection);

        modal.select('.modal')
            .attr('class', 'modal fillL col6');

        var introModal = modal.select('.content');

        introModal.attr('class','cf');

        introModal.append('div')
            .attr('class', 'modal-section')
            .append('h3')
                .text(t('restore.heading'));

        introModal.append('div')
            .attr('class','modal-section')
            .append('p')
                .text(t('restore.description'));

        var buttonWrap = introModal.append('div')
            .attr('class', 'modal-actions cf');

        var restore = buttonWrap.append('button')
            .attr('class', 'restore col6')
            .text(t('restore.restore'))
            .on('click', function() {
                context.history().restore();
                modal.remove();
            });

        buttonWrap.append('button')
            .attr('class', 'reset col6')
            .text(t('restore.reset'))
            .on('click', function() {
                context.history().clearSaved();
                modal.remove();
            });

        restore.node().focus();
    };
        modal.select('button.close').attr('class','hide');

};
<commit_msg>Clean up<commit_after>iD.ui.Restore = function(context) {
    return function(selection) {
        if (!context.history().lock() || !context.history().restorableChanges())
            return;

        var modal = iD.ui.modal(selection);

        modal.select('.modal')
            .attr('class', 'modal fillL col6');

        var introModal = modal.select('.content');

        introModal.attr('class','cf');

        introModal.append('div')
            .attr('class', 'modal-section')
            .append('h3')
            .text(t('restore.heading'));

        introModal.append('div')
            .attr('class','modal-section')
            .append('p')
            .text(t('restore.description'));

        var buttonWrap = introModal.append('div')
            .attr('class', 'modal-actions cf');

        var restore = buttonWrap.append('button')
            .attr('class', 'restore col6')
            .text(t('restore.restore'))
            .on('click', function() {
                context.history().restore();
                modal.remove();
            });

        buttonWrap.append('button')
            .attr('class', 'reset col6')
            .text(t('restore.reset'))
            .on('click', function() {
                context.history().clearSaved();
                modal.remove();
            });

        restore.node().focus();
    };
};
<|endoftext|>


<commit_before>
'use strict';

const _ = require('lodash');
const Users = require('./users');
const toId = require('../toId');
const parser = require('./parser');
const config = require('./config');
const db = require('./db');
const jwt = require('jsonwebtoken');
const got = require('got');
const moment = require('moment');
const winston = require('winston');

const TEN_MINUTE_LIMIT = 600000;

let chatHistory = [];
let isPlaying = false;
let videoQueue = [];
let videoQueueIps = {};
let currentVideo = {videoId: '', host: '', start: 0};

function sockets(io) {
  io.on('connection', (socket) => {
    connection(io, socket);
  });
}

function connection(io, socket) {
  function handleAddUser(username) {
    if (!socket.userId) {
      socket.userId = Users.create(username, socket);
    } else {
      Users.get(socket.userId).setName(username);
    }

    io.emit('update userlist', Users.list());
  }

  socket.on('initial load', () => {
    socket.emit('update messages', chatHistory);
    socket.emit('update userlist', Users.list());
    if (isPlaying) socket.emit('next video', currentVideo);
  });

  socket.on('add user', (data) => {
    if (!_.isObject(data) || (_.isObject(data) && !data.name)) return;
    const username = data.name;
    const userId = toId(username);
    if (!userId || userId.length > 19) return;

    if (socket.userId === userId) {
      Users.get(socket.userId).setName(username);
      return io.emit('update userlist', Users.list());
    }

    if (Users.get(userId)) return;

    if (db('users').get(userId)) {
      if (!data.token) return;
      jwt.verify(data.token, config.jwtSecret, (err, decoded) => {
        if (err) return socket.emit('error token');
        if (decoded.userId !== userId) return;
        handleAddUser(username);
      });
    } else {
      handleAddUser(username);
    }
  });

  /**
   * A chat message event.
   *
   * @params {Object} msg - {text: String, username: String}
   */

  socket.on('chat message', (msg) => {
    if (!_.isObject(msg)) return;
    if (!_.isString(msg.username) || !_.isString(msg.text)) return;
    if (!msg.username || !msg.text) return;
    if (!socket.userId || toId(msg.username) !== socket.userId) return;
    const emit = (data) => socket.emit('chat message', data);
    const message = parser(msg.text, Users.get(socket.userId), emit);
    if (!message) return;
    pushToChatHistory(message);
    io.emit('chat message', message);
  });

  socket.on('add video', (data) => {
    if (!_.isString(data)) return;
    if (!data || data.length > 300) return;
    if (!socket.userId) return;
    const videoId = validateVideo(data);
    if (!videoId) return;
    const user = Users.get(socket.userId);
    if (videoQueueIps[user.ip]) {
      return socket.emit('chat message', {
        text: 'You already have a video in the queue.',
        className: 'text-danger'
      });
    }
    const url = 'https://www.googleapis.com/youtube/v3/videos?id=' + videoId + '&key=' + config.googleAPIKey + '&part=snippet,contentDetails';
    got(url).then(response => {
      const json = JSON.parse(response.body);
      if (!json.items.length) {
        return socket.emit('chat message', {
          text: 'This video does not exist.',
          className: 'text-danger'
        });
      }
      const duration = json.items[0].contentDetails.duration;
      const ms = moment.duration(duration).asMilliseconds();
      if (ms > TEN_MINUTE_LIMIT) {
        return socket.emit('chat message', {
          text: 'Video is too long. The limit is 10 minutes.',
          className: 'text-danger'
        });
      }
      videoQueue.unshift({videoId, host: user.name, ip: user.ip, duration: ms});
      videoQueueIps[user.ip] = true;
      socket.emit('chat message', {
        text: 'Video added to the queue.',
        className: 'text-success'
      });
      if (!isPlaying) {
        isPlaying = true;
        nextVideo((video) => io.emit('next video', video));
      }
    }).catch(error => winston.error(error.response.body));
  });

  function removeUser() {
    Users.remove(socket.userId);
    io.emit('update userlist', Users.list());
  }

  socket.on('logout', removeUser);
  socket.on('disconnect', removeUser);
}

function pushToChatHistory(message) {
  if (chatHistory.length === 100) {
    chatHistory.shift();
  }
  chatHistory.push(message);
}

function nextVideo(emitVideo) {
  const video = videoQueue.pop();
  delete videoQueueIps[video.ip];
  currentVideo = {videoId: video.videoId, host: video.host, start: Date.now()};
  emitVideo(currentVideo);
  setTimeout(() => {
    if (videoQueue.length) {
      nextVideo(emitVideo);
    } else {
      resetVideo(emitVideo);
    }
  }, video.duration);
}

function resetVideo(emitVideo) {
  isPlaying = false;
  currentVideo = {videoId: '', host: '', start: 0};
  emitVideo(currentVideo);
}

function validateVideo(data) {
  const parts = data.split('=');
  if (parts.length < 2) return;
  if (parts[1].indexOf('&') >= 0) parts[1] = parts[1].split('&')[0];
  const videoId = parts[1].trim();
  if (!videoId) return;
  return videoId;
}

module.exports = sockets;
<commit_msg>Fix edge case of when a user logouts and chooses a name<commit_after>
'use strict';

const _ = require('lodash');
const Users = require('./users');
const toId = require('../toId');
const parser = require('./parser');
const config = require('./config');
const db = require('./db');
const jwt = require('jsonwebtoken');
const got = require('got');
const moment = require('moment');
const winston = require('winston');

const TEN_MINUTE_LIMIT = 600000;

let chatHistory = [];
let isPlaying = false;
let videoQueue = [];
let videoQueueIps = {};
let currentVideo = {videoId: '', host: '', start: 0};

function sockets(io) {
  io.on('connection', (socket) => {
    connection(io, socket);
  });
}

function connection(io, socket) {
  function handleAddUser(username) {
    if (!socket.userId || !Users.get(socket.userId)) {
      socket.userId = Users.create(username, socket);
    } else {
      Users.get(socket.userId).setName(username);
    }

    io.emit('update userlist', Users.list());
  }

  socket.on('initial load', () => {
    socket.emit('update messages', chatHistory);
    socket.emit('update userlist', Users.list());
    if (isPlaying) socket.emit('next video', currentVideo);
  });

  socket.on('add user', (data) => {
    if (!_.isObject(data) || (_.isObject(data) && !data.name)) return;
    const username = data.name;
    const userId = toId(username);
    if (!userId || userId.length > 19) return;

    if (socket.userId === userId) {
      return handleAddUser(username);
    }

    if (Users.get(userId)) return;

    if (db('users').get(userId)) {
      if (!data.token) return;
      jwt.verify(data.token, config.jwtSecret, (err, decoded) => {
        if (err) return socket.emit('error token');
        if (decoded.userId !== userId) return;
        handleAddUser(username);
      });
    } else {
      handleAddUser(username);
    }
  });

  /**
   * A chat message event.
   *
   * @params {Object} msg - {text: String, username: String}
   */

  socket.on('chat message', (msg) => {
    if (!_.isObject(msg)) return;
    if (!_.isString(msg.username) || !_.isString(msg.text)) return;
    if (!msg.username || !msg.text) return;
    if (!socket.userId || toId(msg.username) !== socket.userId) return;
    const emit = (data) => socket.emit('chat message', data);
    const message = parser(msg.text, Users.get(socket.userId), emit);
    if (!message) return;
    pushToChatHistory(message);
    io.emit('chat message', message);
  });

  socket.on('add video', (data) => {
    if (!_.isString(data)) return;
    if (!data || data.length > 300) return;
    if (!socket.userId) return;
    const videoId = validateVideo(data);
    if (!videoId) return;
    const user = Users.get(socket.userId);
    if (videoQueueIps[user.ip]) {
      return socket.emit('chat message', {
        text: 'You already have a video in the queue.',
        className: 'text-danger'
      });
    }
    const url = 'https://www.googleapis.com/youtube/v3/videos?id=' + videoId + '&key=' + config.googleAPIKey + '&part=snippet,contentDetails';
    got(url).then(response => {
      const json = JSON.parse(response.body);
      if (!json.items.length) {
        return socket.emit('chat message', {
          text: 'This video does not exist.',
          className: 'text-danger'
        });
      }
      const duration = json.items[0].contentDetails.duration;
      const ms = moment.duration(duration).asMilliseconds();
      if (ms > TEN_MINUTE_LIMIT) {
        return socket.emit('chat message', {
          text: 'Video is too long. The limit is 10 minutes.',
          className: 'text-danger'
        });
      }
      videoQueue.unshift({videoId, host: user.name, ip: user.ip, duration: ms});
      videoQueueIps[user.ip] = true;
      socket.emit('chat message', {
        text: 'Video added to the queue.',
        className: 'text-success'
      });
      if (!isPlaying) {
        isPlaying = true;
        nextVideo((video) => io.emit('next video', video));
      }
    }).catch(error => winston.error(error.response.body));
  });

  function removeUser() {
    Users.remove(socket.userId);
    io.emit('update userlist', Users.list());
  }

  socket.on('logout', removeUser);
  socket.on('disconnect', removeUser);
}

function pushToChatHistory(message) {
  if (chatHistory.length === 100) {
    chatHistory.shift();
  }
  chatHistory.push(message);
}

function nextVideo(emitVideo) {
  const video = videoQueue.pop();
  delete videoQueueIps[video.ip];
  currentVideo = {videoId: video.videoId, host: video.host, start: Date.now()};
  emitVideo(currentVideo);
  setTimeout(() => {
    if (videoQueue.length) {
      nextVideo(emitVideo);
    } else {
      resetVideo(emitVideo);
    }
  }, video.duration);
}

function resetVideo(emitVideo) {
  isPlaying = false;
  currentVideo = {videoId: '', host: '', start: 0};
  emitVideo(currentVideo);
}

function validateVideo(data) {
  const parts = data.split('=');
  if (parts.length < 2) return;
  if (parts[1].indexOf('&') >= 0) parts[1] = parts[1].split('&')[0];
  const videoId = parts[1].trim();
  if (!videoId) return;
  return videoId;
}

module.exports = sockets;
<|endoftext|>


<commit_before>/*!
 * jQuery Mobile
 * http://jquerymobile.com/
 *
 * Copyright 2010, jQuery Project
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 */
(function( jQuery, window, undefined ) {
	// if we're missing support for any of these, then we're a C-grade browser
	if ( !jQuery.support.display || !jQuery.support.position || !jQuery.support.overflow || !jQuery.support.floatclear ) {
		return;
	}

	//these properties should be made easy to override externally
	jQuery.mobile = {};

	jQuery.extend(jQuery.mobile, {
		subPageUrlKey: 'ui-page' //define the key used in urls for sub-pages. Defaults to &ui-page=
	});
	jQuery.mobile.subPageUrlKey = 'ui-page';

	var $window = jQuery(window),
		$html = jQuery('html'),
		$head = jQuery('head'),
		$body,
		$loader = jQuery('

loading.

'),
		startPage,
		startPageId = 'ui-page-start',
		activePageClass = 'ui-page-active',
		pageTransition,
		transitions = 'slide slideup slidedown pop flip fade dissolve swap',
		transitionDuration = 350,
		backBtnText = "Back",
		urlStack = [ {
			url: location.hash.replace( /^#/, "" ),
			transition: "slide"
		} ],
		nextPageRole = null,
		noCache = '.ui-dialog';

	// hide address bar
	function hideBrowserChrome() {
		// prevent scrollstart and scrollstop events
		jQuery.event.special.scrollstart.enabled = false;
		window.scrollTo( 0, 0 );
		setTimeout(function() {
			jQuery.event.special.scrollstart.enabled = true;
		}, 150 );
	}

	function manageGlobalNav(activePage){
		if(activePage.is('.ui-globalnav-expanded')){
			jQuery('[data-role="globalnav"]').addClass('ui-globalnav-disable');
		}
		else{
			jQuery('[data-role="globalnav"]').removeClass('ui-globalnav-disable');
		}
	}

	// send a link through hash tracking
	jQuery.fn.ajaxClick = function() {
		var href = jQuery( this ).attr( "href" );
		pageTransition = jQuery( this ).attr( "data-transition" ) || "slide";
		nextPageRole = jQuery( this ).attr( "data-rel" );

		// let the hashchange event handler take care of everything else
		location.hash = href;

		// note: if it's a non-local-anchor and Ajax is not supported, go to page
		if ( href.match( /^[^#]/ ) && !jQuery.support.ajax ) {
			window.location = href;
		}

		return this;
	};

	// ajaxify all navigable links
	jQuery( "a:not([href=#]):not([target=_blank]):not([rel=external])" ).live( "click", function() {
		jQuery( this ).ajaxClick();
		return false;
	});

	// turn on/off page loading message.. also hides the ui-content div
	function pageLoading( done ) {
		if ( done ) {
			$html.removeClass( "ui-loading" );
			//fade in page content, remove loading msg
			jQuery('.ui-page-active .ui-content').addClass('dissolve in');
		} else {
			$html.addClass( "ui-loading" );
			$loader.appendTo( $body ).addClass( "dissolve in" );
		}
	};

	// transition between pages - based on transitions from jQtouch
	function changePage( from, to, transition, back ) {
		hideBrowserChrome();

		// kill keyboard (thx jQtouch :) )
		jQuery( document.activeElement ).blur();

		// animate in / out
		from.addClass( transition + " out " + ( back ? "reverse" : "" ) );
		to.appendTo($body).addClass( activePageClass + " " + transition +
			" in " + ( back ? "reverse" : "" ) );
		//make sure globalnav is on top
		jQuery('[data-role="globalnav"]').appendTo($body);
		manageGlobalNav(to);

		// callback - remove classes, etc
		to.animationComplete(function() {
			from.add( to ).removeClass(" out in reverse " + transitions );
			from.removeClass( activePageClass );
			pageLoading( true );
			jQuery.fixedToolbars.show();
		});
	};

	jQuery(function() {
		$body = jQuery( "body" );
		pageLoading();

		// needs to be bound at domready (for IE6)
		// find or load content, make it active
		$window.bind( "hashchange", function(e) {
			var url = location.hash.replace( /^#/, "" ),
				stackLength = urlStack.length,
				// pageTransition only exists if the user clicked a link
				back = !pageTransition && stackLength > 1 &&
					urlStack[ stackLength - 2 ].url === url,
				transition = pageTransition || "slide",
				fileUrl = url;
			pageTransition = undefined;

			// if the new href is the same as the previous one
			if ( back ) {
				transition = urlStack.pop().transition;
			} else {
				urlStack.push({ url: url, transition: transition });
			}

			//remove any pages that shouldn't cache
			jQuery(noCache).remove();

			//function for setting role of next page
			function setPageRole( newPage ) {
				if ( nextPageRole ) {
					newPage.attr( "data-role", nextPageRole );
					nextPageRole = undefined;
				}
			}

			if ( url ) {
				// see if content is present already
				var localDiv = jQuery( "[id='" + url + "']" );
				if ( localDiv.length ) {
					if ( localDiv.is( "[data-role]" ) ) {
						setPageRole( localDiv );
					}
					mobilize( localDiv );
					changePage( jQuery( ".ui-page-active" ), localDiv, transition, back );
				} else { //ajax it in
					pageLoading();
					if(url.match( '&' + jQuery.mobile.subPageUrlKey )){
						fileUrl = url.split( '&' + jQuery.mobile.subPageUrlKey )[0];
					}
					var newPage = jQuery( "
" )
						.appendTo( $body )
						.load( fileUrl + ' [data-role="page"]', function() {
							// TODO: test this (avoids querying the dom for new element):
//							var newPage = jQuery( this ).find( ".ui-page" ).eq( 0 )
//								.attr( "id", url );
//							jQuery( this ).replaceWith( newPage );
//							setPageRole( newPage );
//							mobilize( newPage );
//							changePage( jQuery( ".ui-page-active" ), newPage, transition, back );
							jQuery( this ).replaceWith(
								jQuery( this ).find( '[data-role="page"]' ).eq( 0 ).attr( "id", fileUrl ) );
							var newPage = jQuery( "[id='" + fileUrl + "']" );
							setPageRole( newPage );
							mobilize( newPage );
							newPage = jQuery( "[id='" + url + "']" );
							changePage( jQuery( ".ui-page-active" ), newPage, transition, back );
						});
				}
			} else {
				// either we've backed up to the root page url
				// or it's the first page load with no hash present
				var currentPage = jQuery( ".ui-page-active" );
				if ( currentPage.length && !startPage.is( ".ui-page-active" ) ) {
					changePage( currentPage, startPage, transition, back );
				} else {
					startPage.addClass( activePageClass );
					manageGlobalNav(startPage);
					jQuery.fixedToolbars.show();
					pageLoading( true );
				}
			}
		});

		hideBrowserChrome();
	});

	//add orientation class on flip/resize.
	$window.bind( "orientationchange", function( event, data ) {
		$html.removeClass( "portrait landscape" ).addClass( data.orientation );
	});

	//add mobile, loading classes to doc
	$html.addClass('ui-mobile');

	//insert mobile meta (any other metas needed? webapp? iPhone icon? etc)
	$head.append('<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0" />');

	//potential (probably incomplete) fallback to workaround lack of animation callbacks.
	//should this be extended into a full special event?
	// note: Expects CSS animations use transitionDuration (350ms)
	jQuery.fn.animationComplete = function(callback){
		if(jQuery.support.WebKitAnimationEvent){
			return jQuery(this).one('webkitAnimationEnd', callback); //check out transitionEnd (opera per Paul's request)
		}
		else{
			setTimeout(callback, transitionDuration);
		}
	};

	//markup-driven enhancements, to be called on any ui-page upon loading
	function mobilize($el){
		//to-do: make sure this only runs one time on a page (or maybe per component)
		return $el.not('[data-mobilized]').each(function(){

			//hide no-js content
			$el.find('[data-role="nojs"]').addClass('ui-nojs');

			//pre-find data els
			var $dataEls = $el.find('[data-role]:not([data-role="nojs"])').andSelf();

			//header,content,footer
			$dataEls.filter('[data-role="page"]').addClass('ui-page');
			$dataEls.filter('[data-role="header"]').addClass('ui-header');
			$dataEls.filter('[data-role="content"]').addClass('ui-content');
			$dataEls.filter('[data-role="footer"]').addClass('ui-footer');

			//fix toolbars
			$el.fixHeaderFooter();

			//collapsible groupings
			$dataEls.filter('[data-role="collapsible"]').collapsible();
			//single-field separators
			$dataEls.filter('[data-role="fieldcontain"]').fieldcontain();
			//global nav
			$dataEls.filter('[data-role="globalnav"]').globalnav();
			//listview
			$dataEls.filter('[data-role="listview"]').listview();
			//dialog
			$dataEls.filter('[data-role="dialog"]').dialog();
			//ajaxform plugin
			$dataEls.filter('[data-role="ajaxform"]').ajaxform();

			//form elements
			var checksradios = 'input[type=radio],input[type=checkbox]',
				buttonInputs = 'button, input[type=submit], input[type=reset], input[type=image]',
				textInputs = 'input[type=text],input[type=number],input[type=password],textarea',
				sliders = '[data-role="slider"]',
				selects = 'select';

			$formEls = $el.find( [checksradios, buttonInputs, textInputs, sliders, selects].join(',') );

			$formEls.filter(checksradios).customCheckboxRadio();
			//custom buttons
			$formEls.filter(buttonInputs).customButton();
			//custom text inputs
			$formEls.filter(textInputs).customTextInput();
			//sliders
			$formEls.filter(sliders).slider();
			//selects
			$formEls.filter(selects).customSelect();

			//vertical controlgroups
			$dataEls.filter('[data-role="controlgroup"]:not([data-type="horizontal"])').controlgroup();
			//horizontal controlgroups
			$dataEls.filter('[data-role="controlgroup"][data-type="horizontal"]').controlgroup({direction: 'horizontal'});

			//add back buttons to headers that don't have them
			var backBtn = $el.find('.ui-header a.ui-back');
			if(!backBtn.length){
				backBtn = jQuery('').appendTo($el.find('.ui-header'));
			}

			//buttons from links in headers,footers,bars, or with data-role
			$dataEls.filter('[data-role="button"]').add('.ui-header a, .ui-footer a, .ui-bar a').not('.ui-btn').buttonMarkup();
			//links within content areas
			$el.find('.ui-body a:not(.ui-btn):not(.ui-link-inherit)').addClass('ui-link');
			//make all back buttons mimic the back button (pre-js, these links are usually "home" links)
			backBtn
				.click(function(){
					history.go(-1);
					return false;
				})
				.find('.ui-btn-text').text(backBtnText);

			$el.attr('data-mobilized',true);
		});
	};

	jQuery.extend({
		mobilize: mobilize,
		pageLoading: pageLoading,
		changePage: changePage,
		hideBrowserChrome: hideBrowserChrome
	});

	//dom-ready
	jQuery(function(){

		//set up active page - mobilize it!
		startPage = jQuery('[data-role="page"]:first');

		//make sure it has an ID - for finding it later
		if(!startPage.attr('id')){
			startPage.attr('id', startPageId);
		}

		//mobilize all pages present
		mobilize(jQuery('[data-role="page"]'));

		//trigger a new hashchange, hash or not
		$window.trigger( "hashchange" );

		//update orientation
		$html.addClass( jQuery.event.special.orientationchange.orientation( $window ) );

		//some debug stuff for the events pages
		jQuery('body').bind('scrollstart scrollstop swipe swipeleft swiperight tap taphold turn',function(e){
			jQuery('#eventlogger').prepend('
Event fired: '+ e.type +'
');
		});
	});
})( jQuery, this );
<commit_msg>Refactored mobilize to iterate over dataEls just once, removing about 10 filter calls.<commit_after>/*!
 * jQuery Mobile
 * http://jquerymobile.com/
 *
 * Copyright 2010, jQuery Project
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 */
(function( jQuery, window, undefined ) {
	// if we're missing support for any of these, then we're a C-grade browser
	if ( !jQuery.support.display || !jQuery.support.position || !jQuery.support.overflow || !jQuery.support.floatclear ) {
		return;
	}

	//these properties should be made easy to override externally
	jQuery.mobile = {};

	jQuery.extend(jQuery.mobile, {
		subPageUrlKey: 'ui-page' //define the key used in urls for sub-pages. Defaults to &ui-page=
	});
	jQuery.mobile.subPageUrlKey = 'ui-page';

	var $window = jQuery(window),
		$html = jQuery('html'),
		$head = jQuery('head'),
		$body,
		$loader = jQuery('

loading.

'),
		startPage,
		startPageId = 'ui-page-start',
		activePageClass = 'ui-page-active',
		pageTransition,
		transitions = 'slide slideup slidedown pop flip fade dissolve swap',
		transitionDuration = 350,
		backBtnText = "Back",
		urlStack = [ {
			url: location.hash.replace( /^#/, "" ),
			transition: "slide"
		} ],
		nextPageRole = null,
		noCache = '.ui-dialog';

	// hide address bar
	function hideBrowserChrome() {
		// prevent scrollstart and scrollstop events
		jQuery.event.special.scrollstart.enabled = false;
		window.scrollTo( 0, 0 );
		setTimeout(function() {
			jQuery.event.special.scrollstart.enabled = true;
		}, 150 );
	}

	function manageGlobalNav(activePage){
		if(activePage.is('.ui-globalnav-expanded')){
			jQuery('[data-role="globalnav"]').addClass('ui-globalnav-disable');
		}
		else{
			jQuery('[data-role="globalnav"]').removeClass('ui-globalnav-disable');
		}
	}

	// send a link through hash tracking
	jQuery.fn.ajaxClick = function() {
		var href = jQuery( this ).attr( "href" );
		pageTransition = jQuery( this ).attr( "data-transition" ) || "slide";
		nextPageRole = jQuery( this ).attr( "data-rel" );

		// let the hashchange event handler take care of everything else
		location.hash = href;

		// note: if it's a non-local-anchor and Ajax is not supported, go to page
		if ( href.match( /^[^#]/ ) && !jQuery.support.ajax ) {
			window.location = href;
		}

		return this;
	};

	// ajaxify all navigable links
	jQuery( "a:not([href=#]):not([target=_blank]):not([rel=external])" ).live( "click", function() {
		jQuery( this ).ajaxClick();
		return false;
	});

	// turn on/off page loading message.. also hides the ui-content div
	function pageLoading( done ) {
		if ( done ) {
			$html.removeClass( "ui-loading" );
			//fade in page content, remove loading msg
			jQuery('.ui-page-active .ui-content').addClass('dissolve in');
		} else {
			$html.addClass( "ui-loading" );
			$loader.appendTo( $body ).addClass( "dissolve in" );
		}
	};

	// transition between pages - based on transitions from jQtouch
	function changePage( from, to, transition, back ) {
		hideBrowserChrome();

		// kill keyboard (thx jQtouch :) )
		jQuery( document.activeElement ).blur();

		// animate in / out
		from.addClass( transition + " out " + ( back ? "reverse" : "" ) );
		to.appendTo($body).addClass( activePageClass + " " + transition +
			" in " + ( back ? "reverse" : "" ) );
		//make sure globalnav is on top
		jQuery('[data-role="globalnav"]').appendTo($body);
		manageGlobalNav(to);

		// callback - remove classes, etc
		to.animationComplete(function() {
			from.add( to ).removeClass(" out in reverse " + transitions );
			from.removeClass( activePageClass );
			pageLoading( true );
			jQuery.fixedToolbars.show();
		});
	};

	jQuery(function() {
		$body = jQuery( "body" );
		pageLoading();

		// needs to be bound at domready (for IE6)
		// find or load content, make it active
		$window.bind( "hashchange", function(e) {
			var url = location.hash.replace( /^#/, "" ),
				stackLength = urlStack.length,
				// pageTransition only exists if the user clicked a link
				back = !pageTransition && stackLength > 1 &&
					urlStack[ stackLength - 2 ].url === url,
				transition = pageTransition || "slide",
				fileUrl = url;
			pageTransition = undefined;

			// if the new href is the same as the previous one
			if ( back ) {
				transition = urlStack.pop().transition;
			} else {
				urlStack.push({ url: url, transition: transition });
			}

			//remove any pages that shouldn't cache
			jQuery(noCache).remove();

			//function for setting role of next page
			function setPageRole( newPage ) {
				if ( nextPageRole ) {
					newPage.attr( "data-role", nextPageRole );
					nextPageRole = undefined;
				}
			}

			if ( url ) {
				// see if content is present already
				var localDiv = jQuery( "[id='" + url + "']" );
				if ( localDiv.length ) {
					if ( localDiv.is( "[data-role]" ) ) {
						setPageRole( localDiv );
					}
					mobilize( localDiv );
					changePage( jQuery( ".ui-page-active" ), localDiv, transition, back );
				} else { //ajax it in
					pageLoading();
					if(url.match( '&' + jQuery.mobile.subPageUrlKey )){
						fileUrl = url.split( '&' + jQuery.mobile.subPageUrlKey )[0];
					}
					var newPage = jQuery( "
" )
						.appendTo( $body )
						.load( fileUrl + ' [data-role="page"]', function() {
							// TODO: test this (avoids querying the dom for new element):
//							var newPage = jQuery( this ).find( ".ui-page" ).eq( 0 )
//								.attr( "id", url );
//							jQuery( this ).replaceWith( newPage );
//							setPageRole( newPage );
//							mobilize( newPage );
//							changePage( jQuery( ".ui-page-active" ), newPage, transition, back );
							jQuery( this ).replaceWith(
								jQuery( this ).find( '[data-role="page"]' ).eq( 0 ).attr( "id", fileUrl ) );
							var newPage = jQuery( "[id='" + fileUrl + "']" );
							setPageRole( newPage );
							mobilize( newPage );
							newPage = jQuery( "[id='" + url + "']" );
							changePage( jQuery( ".ui-page-active" ), newPage, transition, back );
						});
				}
			} else {
				// either we've backed up to the root page url
				// or it's the first page load with no hash present
				var currentPage = jQuery( ".ui-page-active" );
				if ( currentPage.length && !startPage.is( ".ui-page-active" ) ) {
					changePage( currentPage, startPage, transition, back );
				} else {
					startPage.addClass( activePageClass );
					manageGlobalNav(startPage);
					jQuery.fixedToolbars.show();
					pageLoading( true );
				}
			}
		});

		hideBrowserChrome();
	});

	//add orientation class on flip/resize.
	$window.bind( "orientationchange", function( event, data ) {
		$html.removeClass( "portrait landscape" ).addClass( data.orientation );
	});

	//add mobile, loading classes to doc
	$html.addClass('ui-mobile');

	//insert mobile meta (any other metas needed? webapp? iPhone icon? etc)
	$head.append('<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0" />');

	//potential (probably incomplete) fallback to workaround lack of animation callbacks.
	//should this be extended into a full special event?
	// note: Expects CSS animations use transitionDuration (350ms)
	jQuery.fn.animationComplete = function(callback){
		if(jQuery.support.WebKitAnimationEvent){
			return jQuery(this).one('webkitAnimationEnd', callback); //check out transitionEnd (opera per Paul's request)
		}
		else{
			setTimeout(callback, transitionDuration);
		}
	};

	//markup-driven enhancements, to be called on any ui-page upon loading
	function mobilize($el){
		//to-do: make sure this only runs one time on a page (or maybe per component)
		return $el.not('[data-mobilized]').each(function(){

			//hide no-js content
			$el.find('[data-role="nojs"]').addClass('ui-nojs');

			//pre-find data els
			var $dataEls = $el.find('[data-role]').andSelf().each(function() {
				var $this = $(this),
					role = $this.attr("data-role");
				switch(role) {
				case 'page':
				case 'header':
				case 'content':
				case 'footer':
					$this.addClass('ui-' + role);
					break;
				case 'collpsible':
				case 'fieldcontain':
				case 'globalnav':
				case 'listview':
				case 'dialog':
				case 'ajaxform':
					$this[role]();
					break;
				}
			});

			//fix toolbars
			$el.fixHeaderFooter();

			//form elements
			var checksradios = 'input[type=radio],input[type=checkbox]',
				buttonInputs = 'button, input[type=submit], input[type=reset], input[type=image]',
				textInputs = 'input[type=text],input[type=number],input[type=password],textarea',
				sliders = '[data-role="slider"]',
				selects = 'select';

			$formEls = $el.find( [checksradios, buttonInputs, textInputs, sliders, selects].join(',') );

			$formEls.filter(checksradios).customCheckboxRadio();
			//custom buttons
			$formEls.filter(buttonInputs).customButton();
			//custom text inputs
			$formEls.filter(textInputs).customTextInput();
			//sliders
			$formEls.filter(sliders).slider();
			//selects
			$formEls.filter(selects).customSelect();

			//vertical controlgroups
			$dataEls.filter('[data-role="controlgroup"]:not([data-type="horizontal"])').controlgroup();
			//horizontal controlgroups
			$dataEls.filter('[data-role="controlgroup"][data-type="horizontal"]').controlgroup({direction: 'horizontal'});

			//add back buttons to headers that don't have them
			var backBtn = $el.find('.ui-header a.ui-back');
			if(!backBtn.length){
				backBtn = jQuery('').appendTo($el.find('.ui-header'));
			}

			//buttons from links in headers,footers,bars, or with data-role
			$dataEls.filter('[data-role="button"]').add('.ui-header a, .ui-footer a, .ui-bar a').not('.ui-btn').buttonMarkup();
			//links within content areas
			$el.find('.ui-body a:not(.ui-btn):not(.ui-link-inherit)').addClass('ui-link');
			//make all back buttons mimic the back button (pre-js, these links are usually "home" links)
			backBtn
				.click(function(){
					history.go(-1);
					return false;
				})
				.find('.ui-btn-text').text(backBtnText);

			$el.attr('data-mobilized',true);
		});
	};

	jQuery.extend({
		mobilize: mobilize,
		pageLoading: pageLoading,
		changePage: changePage,
		hideBrowserChrome: hideBrowserChrome
	});

	//dom-ready
	jQuery(function(){

		//set up active page - mobilize it!
		startPage = jQuery('[data-role="page"]:first');

		//make sure it has an ID - for finding it later
		if(!startPage.attr('id')){
			startPage.attr('id', startPageId);
		}

		//mobilize all pages present
		mobilize(jQuery('[data-role="page"]'));

		//trigger a new hashchange, hash or not
		$window.trigger( "hashchange" );

		//update orientation
		$html.addClass( jQuery.event.special.orientationchange.orientation( $window ) );

		//some debug stuff for the events pages
		jQuery('body').bind('scrollstart scrollstop swipe swipeleft swiperight tap taphold turn',function(e){
			jQuery('#eventlogger').prepend('
Event fired: '+ e.type +'
');
		});
	});
})( jQuery, this );
<|endoftext|>


<commit_before>'use strict';

var version = 'v-2017-05-11 22:22';
var __DEVELOPMENT__ = false;
var __DEBUG__ = true;
var offlineResources = [
  '/',
  '/index.html'
];

var ignoreCache = [
  /https?:\/\/cdn.bootcss.com\//,
  /https?:\/\/api.github.com\//,
  /https?:\/\/www.google-analytics.com\//,
  /chrome-extension:\//
];
var port;

/**
* common function
*/

function developmentMode() {
  return location.hostname === '127.0.0.1' || location.hostname === 'localhost'
}

function cacheKey() {
  return [version, ...arguments].join(':');
}

function log() {
  if (developmentMode()) {
    console.log("SW:", ...arguments);
  }
}

function shouldAlwaysFetch(request) {
  return __DEVELOPMENT__ ||
    request.method !== 'GET' ||
    ignoreCache.some(regex => request.url.match(regex));
}

function shouldFetchAndCache(request) {
  return (/text\/html/i).test(request.headers.get('Accept'));
}

function sendNotify(title, options, event) {
  if (Notification.permission !== 'granted') {
    log('Not granted Notification permission.');

    if (port && port.postMessage) {
      port.postMessage({
        type: 'applyNotify',
        info: {title, options}
      });
    }

    return;
  }

  var notificationPromise = self.registration.showNotification(title || '轩帅', Object.assign({
    body: '云在青天水在瓶',
    icon: 'https://blog.lovejade.cn/assets/favicons/mstile-144x144.png',
    tag: 'push'
  }, options));

  return event && event.waitUntil(notificationPromise);
}

/**
* onClickNotify
*/

function onClickNotify(event) {
  event.notification.close();
  var url = "https://blog.lovejade.cn/";

  event.waitUntil(
    self.clients.matchAll({
      type: "window"
    })
    .then(() => {
      if (self.clients.openWindow) {
          return self.clients.openWindow(url);
      }
    })
  );
}

/**
* Install
*/

function onInstall(event) {
  log('install event in progress.');

  event.waitUntil(
    caches.open(cacheKey('offline'))
        .then(cache => cache.addAll(offlineResources))
        .then(() => log('installation complete! version: ' + version))
        .then(() => self.skipWaiting())
  );
}

/**
* Fetch
*/

function offlineResponse(request) {
  log('(offline)', request.method, request.url);
  if (request.url.match(/\.(jpg|png|gif|svg|jpeg)(\?.*)?$/)) {
    return caches.match('/wp-content/themes/Kratos/images/default.jpg');
  } else {
    return caches.match('/offline.html');
  }
}

function cachedOrOffline(request) {
  return caches
    .match(request)
    .then((response) => response || offlineResponse(request));
}

function networkedAndCache(request) {
  return fetch(request)
    .then(response => {
      var copy = response.clone();

      caches.open(cacheKey('resources'))
          .then(cache => {
              cache.put(request, copy);
          });

      log("(network: cache write)", request.method, request.url);
      return response;
    });
}

function cachedOrNetworked(request) {
  return caches.match(request)
      .then((response) => {
          log(response ? '(cached)' : '(network: cache miss)', request.method, request.url);
          return response ||
              networkedAndCache(request)
              .catch(() => offlineResponse(request));
      });
}

function networkedOrOffline(request) {
  return fetch(request)
      .then(response => {
          log('(network)', request.method, request.url);
          return response;
      })
      .catch(() => offlineResponse(request));
}

function onFetch(event) {
  var request = event.request;

  if (shouldAlwaysFetch(request)) {
      log('AlwaysFetch request: ', event.request.url);
      event.respondWith(networkedOrOffline(request));
      return;
  }

  if (shouldFetchAndCache(request)) {
      event.respondWith(
          networkedAndCache(request).catch(() => cachedOrOffline(request))
      );
      return;
  }

  event.respondWith(cachedOrNetworked(request));
}

/**
* Activate
*/

function removeOldCache() {
  return caches
      .keys()
      .then(keys =>
          Promise.all(
              keys
              .filter(key => !key.startsWith(version))
              .map(key => caches.delete(key))
          )
      )
      .then(() => {
          log('removeOldCache completed.');
      });
}

function onActivate(event) {
  log('activate event in progress.');
  event.waitUntil(Promise.all([
      self.clients.claim(),
      removeOldCache()
  ]))
}

/**
* onPush
*/

function onPush(event) {
  log('onPush ', event);
  sendNotify('Hi:', {
      body: `onPush${new Date()}？_ ？~`
  }, event);
}

/**
* onSync
*/

function onSync(event) {
  log('onSync', event);
  sendNotify('Hi:', {
      body: `onSync${new Date()}？_ ？ ~`
  }, event);
}

/**
* onMessage
*/

function onMessage(event) {
  log('onMessage', event);

  if (event.ports) {
      port = event.ports[0];
  }

  if (!event.data) {
      return;
  }

  if (event.data.type === 'notify') {
      var {title, options} = event.data.info || {};
      sendNotify(title, options, event);
  }
}

log("Hello from ServiceWorker land!", version);

self.addEventListener('install', onInstall);
self.addEventListener('fetch', onFetch);
self.addEventListener("activate", onActivate);
self.addEventListener("push", onPush);
self.addEventListener("sync", onSync);
self.addEventListener('message', onMessage);
self.addEventListener("notificationclick", onClickNotify);
<commit_msg>✨ update service-worker<commit_after>'use strict';

var version = 'v-2017-05-11 22:22';
var __DEVELOPMENT__ = false;
var __DEBUG__ = true;
var offlineResources = [
  '/',
  '/index.html'
];

var ignoreCache = [
  /https?:\/\/cdn.bootcss.com\//,
  /https?:\/\/api.github.com\//,
  /https?:\/\/www.google-analytics.com\//,
  /https?:\/\/js.t.sinajs.cn\//,
  /https?:\/\/tjs.sjs.sinajs.cn\//,
  /https?:\/\/image.nicelinks.site\//,
  /webpack:\//,
  /chrome-extension:\//
];
var port;

/**
* common function
*/

function developmentMode() {
  return location.hostname === '127.0.0.1' || location.hostname === 'localhost'
}

function cacheKey() {
  return [version, ...arguments].join(':');
}

function log() {
  if (developmentMode()) {
    console.log("SW:", ...arguments);
  }
}

function shouldAlwaysFetch(request) {
  return __DEVELOPMENT__ ||
    request.method !== 'GET' ||
    ignoreCache.some(regex => request.url.match(regex));
}

function shouldFetchAndCache(request) {
  return (/text\/html/i).test(request.headers.get('Accept'));
}

function sendNotify(title, options, event) {
  if (Notification.permission !== 'granted') {
    log('Not granted Notification permission.');

    if (port && port.postMessage) {
      port.postMessage({
        type: 'applyNotify',
        info: {title, options}
      });
    }

    return;
  }

  var notificationPromise = self.registration.showNotification(title || '轩帅', Object.assign({
    body: '云在青天水在瓶',
    icon: 'https://blog.lovejade.cn/assets/favicons/mstile-144x144.png',
    tag: 'push'
  }, options));

  return event && event.waitUntil(notificationPromise);
}

/**
* onClickNotify
*/

function onClickNotify(event) {
  event.notification.close();
  var url = "https://blog.lovejade.cn/";

  event.waitUntil(
    self.clients.matchAll({
      type: "window"
    })
    .then(() => {
      if (self.clients.openWindow) {
          return self.clients.openWindow(url);
      }
    })
  );
}

/**
* Install
*/

function onInstall(event) {
  log('install event in progress.');

  event.waitUntil(
    caches.open(cacheKey('offline'))
        .then(cache => cache.addAll(offlineResources))
        .then(() => log('installation complete! version: ' + version))
        .then(() => self.skipWaiting())
  );
}

/**
* Fetch
*/

function offlineResponse(request) {
  log('(offline)', request.method, request.url);
  if (request.url.match(/\.(jpg|png|gif|svg|jpeg)(\?.*)?$/)) {
    return caches.match('/wp-content/themes/Kratos/images/default.jpg');
  } else {
    return caches.match('/offline.html');
  }
}

function cachedOrOffline(request) {
  return caches
    .match(request)
    .then((response) => response || offlineResponse(request));
}

function networkedAndCache(request) {
  return fetch(request)
    .then(response => {
      var copy = response.clone();

      caches.open(cacheKey('resources'))
          .then(cache => {
              cache.put(request, copy);
          });

      log("(network: cache write)", request.method, request.url);
      return response;
    });
}

function cachedOrNetworked(request) {
  return caches.match(request)
      .then((response) => {
          log(response ? '(cached)' : '(network: cache miss)', request.method, request.url);
          return response ||
              networkedAndCache(request)
              .catch(() => offlineResponse(request));
      });
}

function networkedOrOffline(request) {
  return fetch(request)
      .then(response => {
          log('(network)', request.method, request.url);
          return response;
      })
      .catch(() => offlineResponse(request));
}

function onFetch(event) {
  var request = event.request;

  if (shouldAlwaysFetch(request)) {
      log('AlwaysFetch request: ', event.request.url);
      event.respondWith(networkedOrOffline(request));
      return;
  }

  if (shouldFetchAndCache(request)) {
      event.respondWith(
          networkedAndCache(request).catch(() => cachedOrOffline(request))
      );
      return;
  }

  event.respondWith(cachedOrNetworked(request));
}

/**
* Activate
*/

function removeOldCache() {
  return caches
      .keys()
      .then(keys =>
          Promise.all(
              keys
              .filter(key => !key.startsWith(version))
              .map(key => caches.delete(key))
          )
      )
      .then(() => {
          log('removeOldCache completed.');
      });
}

function onActivate(event) {
  log('activate event in progress.');
  event.waitUntil(Promise.all([
      self.clients.claim(),
      removeOldCache()
  ]))
}

/**
* onPush
*/

function onPush(event) {
  log('onPush ', event);
  sendNotify('Hi:', {
      body: `onPush${new Date()}？_ ？~`
  }, event);
}

/**
* onSync
*/

function onSync(event) {
  log('onSync', event);
  sendNotify('Hi:', {
      body: `onSync${new Date()}？_ ？ ~`
  }, event);
}

/**
* onMessage
*/

function onMessage(event) {
  log('onMessage', event);

  if (event.ports) {
      port = event.ports[0];
  }

  if (!event.data) {
      return;
  }

  if (event.data.type === 'notify') {
      var {title, options} = event.data.info || {};
      sendNotify(title, options, event);
  }
}

log("Hello from ServiceWorker land!", version);

self.addEventListener('install', onInstall);
self.addEventListener('fetch', onFetch);
self.addEventListener("activate", onActivate);
self.addEventListener("push", onPush);
self.addEventListener("sync", onSync);
self.addEventListener('message', onMessage);
self.addEventListener("notificationclick", onClickNotify);
<|endoftext|>