<commit_before>/*jslint browser: true, for: true */
/*global dashjs, flowplayer, MediaPlayer, window */

/*!

   MPEG-DASH engine plugin for Flowplayer HTML5

   Copyright (c) 2015-2017, Flowplayer Drive Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes hls.js
   Copyright (c) 2015 Dailymotion (http://www.dailymotion.com)
   https://github.com/dailymotion/hls.js/blob/master/LICENSE

   Requires Flowplayer HTML5 version 6.x
   $GIT_DESC$

*/
(function () {
    "use strict";
    var extension = function (dashjs, flowplayer) {
        var engineName = "dash",
            mse = window.MediaSource || window.WebKitMediaSource,
            common = flowplayer.common,
            extend = flowplayer.extend,
            version = flowplayer.version,
            coreV6 = version.indexOf("6.") === 0,
            dashconf,

            dashCanPlay = function (sourceType, dashType, dashCodecs) {
                return sourceType.toLowerCase() === "application/dash+xml" &&
                        mse.isTypeSupported(dashType + ';codecs="' + dashCodecs + '"') &&
                        // Android MSE advertises he-aac, but fails
                        (dashCodecs.indexOf("mp4a.40.5") < 0 || navigator.userAgent.indexOf("Android") < 0);
            },

            engineImpl = function dashjsEngine(player, root) {
                var bean = flowplayer.bean,
                    support = flowplayer.support,
                    mediaPlayer,
                    videoTag,
                    handleError = function (errorCode, src, url) {
                        var errobj = {code: errorCode};

                        if (errorCode > 2) {
                            errobj.video = extend(player.video, {
                                src: src,
                                url: url || src
                            });
                        }
                        return errobj;
                    },

                    lastSelectedQuality = -1,
                    initQualitySelection = function (dashQualitiesConf, data) {
                        // multiperiod not supported
                        if (!dashQualitiesConf || !support.inlineVideo ||
                                data.Period_asArray.length > 1 ||
                                (support.browser.safari && !dashconf.qualitiesForSafari)) {
                            return;
                        }

                        var vsets = [],
                            bandwidths = [],
                            qualities = [],
                            qIndices = [];

                        data.Period_asArray[0].AdaptationSet_asArray.forEach(function (aset) {
                            if (aset.par) {
                                aset.Representation_asArray.forEach(function (repr) {
                                    var codecs = repr.mimeType + ";codecs=" + repr.codecs;

                                    if (mse.isTypeSupported(codecs)) {
                                        bandwidths.push(repr.bandwidth);
                                        vsets.push({
                                            bandwidth: repr.bandwidth,
                                            height: repr.height,
                                            width: repr.width
                                        });
                                    }
                                });
                            }
                        });
                        if (bandwidths.length < 2) {
                            return;
                        }
                        bandwidths.sort(function (a, b) {
                            return a - b;
                        });

                        if (dashQualitiesConf !== true) {
                            if (typeof dashQualitiesConf === "string") {
                                dashQualitiesConf.split(/\s*,\s*/).forEach(function (q) {
                                    qIndices.push(parseInt(q, 10));
                                });
                            } else if (typeof dashQualitiesConf !== "boolean") {
                                dashQualitiesConf.forEach(function (q) {
                                    qIndices.push(isNaN(Number(q))
                                        ? q.level
                                        : q);
                                });
                            }
                        }
                        bandwidths.forEach(function (bw) {
                            var levelIndex = 0;

                            vsets.forEach(function (vset) {
                                if (bw === vset.bandwidth &&
                                        (dashQualitiesConf === true || qIndices.indexOf(levelIndex) > -1)) {
                                    qualities.push(levelIndex);
                                }
                                levelIndex += 1;
                            });
                        });
                        if (qualities.length < 2) {
                            return;
                        }

                        if (dashQualitiesConf === true || qIndices.indexOf(-1) > -1) {
                            qualities.unshift(-1);
                            bandwidths.unshift(0);
                        }

                        player.video.qualities = [];
                        qualities.forEach(function (idx) {
                            var level = vsets[idx],
                                q = qIndices.length
                                    ? dashQualitiesConf[qIndices.indexOf(idx)]
                                    : idx,
                                label = q.label || (idx < 0
                                    ? "Auto"
                                    : level.width + "x" + level.height +
                                            " (" + Math.round(level.bandwidth / 1000) + "k)");

                            player.video.qualities.push({value: idx, label: label});
                        });

                        if (qualities.indexOf(lastSelectedQuality) > -1) {
                            mediaPlayer.setAutoSwitchQualityFor("video", lastSelectedQuality < 0);
                            if (lastSelectedQuality > -1) {
                                mediaPlayer.setInitialBitrateFor("video", Math.round(bandwidths[lastSelectedQuality] / 1000));
                            }
                            player.video.quality = lastSelectedQuality;
                        } else {
                            player.video.quality = -1;
                        }
                    },

                    bc,
                    has_bg,

                    engine = {
                        engineName: engineName,

                        pick: function (sources) {
                            var i,
                                source,
                                dashType,
                                dashCodecs;

                            for (i = 0; i < sources.length; i += 1) {
                                source = sources[i];
                                dashType = source.dashType || dashconf.type;
                                dashCodecs = source.dashCodecs || dashconf.codecs;
                                if (dashCanPlay(source.type, dashType, dashCodecs)) {
                                    if (typeof source.src === 'string') {
                                        source.src = common.createAbsoluteUrl(source.src);
                                    }
                                    return source;
                                }
                            }
                        },

                        load: function (video) {
                            var conf = player.conf,
                                dashUpdatedConf = extend(dashconf, conf.dash, video.dash),
                                dashQualitiesConf = video.dashQualities || conf.dashQualities,
                                EVENTS = {
                                    ended: "finish",
                                    loadeddata: "ready",
                                    pause: "pause",
                                    play: "resume",
                                    progress: "buffer",
                                    ratechange: "speed",
                                    seeked: "seek",
                                    timeupdate: "progress",
                                    volumechange: "volume",
                                    error: "error"
                                },
                                DASHEVENTS = dashjs.MediaPlayer.events,
                                autoplay = !!video.autoplay || !!conf.autoplay,
                                posterClass = "is-poster",
                                livestartpos = 0;

                            if (video.dashQualities === false || coreV6) {
                                dashQualitiesConf = false;
                            } else if (dashQualitiesConf === undefined) {
                                dashQualitiesConf = true;
                            }

                            if (!mediaPlayer) {
                                videoTag = common.findDirect("video", root)[0]
                                        || common.find(".fp-player > video", root)[0];

                                if (videoTag) {
                                    // destroy video tag
                                    // otherwise <video autoplay> continues to play
                                    common.find("source", videoTag).forEach(function (source) {
                                        source.removeAttribute("src");
                                    });
                                    videoTag.removeAttribute("src");
                                    videoTag.load();
                                    common.removeNode(videoTag);
                                }

                                // dash.js enforces preload="auto" and
                                // autoplay depending on initialization
                                // so setting the attributes here will have no effect
                                videoTag = common.createElement("video", {
                                    "class": "fp-engine " + engineName + "-engine",
                                    "volume": player.volumeLevel,
                                    "x-webkit-airplay": "allow"
                                });

                                Object.keys(EVENTS).forEach(function (key) {
                                    var flow = EVENTS[key],
                                        type = key + "." + engineName,
                                        arg;

                                    bean.on(videoTag, type, function (e) {
                                        if (conf.debug && flow.indexOf("progress") < 0) {
                                            console.log(type, "->", flow, e.originalEvent);
                                        }

                                        var vct = videoTag.currentTime,
                                            ct = (mediaPlayer.time && mediaPlayer.time()) || vct,
                                            seekable = videoTag.seekable,
                                            buffered = videoTag.buffered,
                                            buffer = 0,
                                            buffend = 0,
                                            errorCode,
                                            i;

                                        switch (flow) {
                                        case "ready":
                                            arg = extend(player.video, {
                                                duration: mediaPlayer.duration(),
                                                seekable: seekable.length && seekable.end(null),
                                                width: videoTag.videoWidth,
                                                height: videoTag.videoHeight,
                                                url: player.video.src
                                            });
                                            break;
                                        case "resume":
                                            if (coreV6 && player.poster) {
                                                common.removeClass(root, posterClass);
                                                player.poster = false;
                                            }
                                            break;
                                        case "seek":
                                            arg = ct;
                                            break;
                                        case "progress":
                                            if (player.live && !player.dvr) {
                                                if (!livestartpos && vct) {
                                                    livestartpos = vct;
                                                }
                                                arg = livestartpos
                                                    ? vct - livestartpos
                                                    : 0;
                                            } else {
                                                arg = ct;
                                            }
                                            break;
                                        case "speed":
                                            // dash.js often triggers playback rate changes
                                            // when adapting bit rate
                                            // except when in debug mode, only
                                            // trigger explicit events via speed method
                                            if (!dashUpdatedConf.debug) {
                                                e.preventDefault();
                                                return;
                                            }
                                            arg = videoTag.playbackRate;
                                            break;
                                        case "volume":
                                            arg = videoTag.volume;
                                            break;
                                        case "buffer":
                                            try {
                                                buffer = buffered.length && buffered.end(null);
                                                if (!player.video.live && ct && buffer) {
                                                    // cycle through time ranges to obtain buffer
                                                    // nearest current time
                                                    for (i = buffered.length - 1; i > -1; i -= 1) {
                                                        buffend = buffered.end(i);

                                                        if (buffend >= ct) {
                                                            buffer = buffend;
                                                        }
                                                    }
                                                }
                                            } catch (ignore) {}
                                            video.buffer = buffer;
                                            arg = buffer;
                                            break;
                                        case "error":
                                            errorCode = videoTag.error && videoTag.error.code;
                                            arg = handleError(errorCode, player.video.src);
                                            break;
                                        }

                                        player.trigger(flow, [player, arg]);
                                    });
                                });

                                player.on("error." + engineName, function () {
                                    if (mediaPlayer) {
                                        player.engine.unload();
                                    }
                                });

                                if (!coreV6) {
                                    player.on("quality." + engineName, function (e, api, q) {
                                        mediaPlayer.setAutoSwitchQualityFor("video", q < 0);
                                        if (q > -1) {
                                            mediaPlayer.setQualityFor("video", q);
                                        }
                                        lastSelectedQuality = q;
                                    });

                                } else if (conf.poster) {
                                    var posterHack = function (e) {
                                        if (e.type === "stop" || !autoplay) {
                                            setTimeout(function () {
                                                if (!player.poster) {
                                                    common.addClass(root, posterClass);
                                                    player.poster = true;
                                                }
                                            });
                                        }
                                    };

                                    player.one("ready." + engineName, posterHack).on("stop." + engineName, posterHack);
                                }

                                common.prepend(common.find(".fp-player", root)[0], videoTag);

                            } else {
                                mediaPlayer.reset();
                            }

                            mediaPlayer = dashjs.MediaPlayer().create();
                            player.engine[engineName] = mediaPlayer;

                            // new ABR algo
                            mediaPlayer.enableBufferOccupancyABR(dashUpdatedConf.bufferOccupancyABR);
                            // caching can cause failures in playlists
                            // for the moment disable entirely
                            mediaPlayer.enableLastBitrateCaching(false);
                            // for seeking in paused state
                            mediaPlayer.setScheduleWhilePaused(true);
                            mediaPlayer.getDebug().setLogToBrowserConsole(dashUpdatedConf.debug);

                            Object.keys(DASHEVENTS).forEach(function (key) {
                                var etype = DASHEVENTS[key],
                                    fpEventType = engineName + etype.charAt(0).toUpperCase() + etype.slice(1),
                                    listeners = dashUpdatedConf.listeners,
                                    expose = listeners && listeners.indexOf(fpEventType) > -1;

                                mediaPlayer.on(etype, function (e) {
                                    var src = player.video.src,
                                        fperr,
                                        errobj;

                                    switch (key) {
                                    case "MANIFEST_LOADED":
                                        initQualitySelection(dashQualitiesConf, e.data);
                                        break;
                                    case "ERROR":
                                        switch (e.error) {
                                        case "download":
                                            fperr = 4;
                                            break;
                                        case "manifestError":
                                            fperr = 5;
                                            break;
                                        case "mediasource":
                                            switch (e.event) {
                                            case "MEDIA_ERR_DECODE":
                                                fperr = 3;
                                                break;
                                            case "MEDIA_ERR_SRC_NOT_SUPPORTED":
                                                fperr = 5;
                                                break;
                                            case "MEDIA_ERR_NETWORK":
                                                fperr = 2;
                                                break;
                                            case "MEDIA_ERR_ABORTED":
                                                fperr = 1;
                                                break;
                                            }
                                            break;
                                        default:
                                            fperr = 5;
                                        }
                                        errobj = handleError(fperr, src, e.event.url);
                                        player.trigger('error', [player, errobj]);
                                        break;
                                    }

                                    if (expose) {
                                        player.trigger(fpEventType, [player, e]);
                                    }
                                });
                            });

                            mediaPlayer.initialize(videoTag, video.src, autoplay);

                            // update video object before ready
                            player.video = video;

                            if (player.paused && autoplay) {
                                if (support.firstframe) {
                                    mediaPlayer.play();
                                } else {
                                    videoTag.play();
                                }
                            }
                        },

                        resume: function () {
                            mediaPlayer.play();
                        },

                        pause: function () {
                            mediaPlayer.pause();
                        },

                        seek: function (time) {
                            mediaPlayer.seek(time);
                        },

                        volume: function (level) {
                            if (videoTag) {
                                videoTag.volume = level;
                            }
                        },

                        speed: function (val) {
                            videoTag.playbackRate = val;
                            // see ratechange/speed event
                            player.trigger('speed', [player, val]);
                        },

                        unload: function () {
                            if (mediaPlayer) {
                                var listeners = "." + engineName;

                                mediaPlayer.reset();
                                mediaPlayer = 0;
                                player.off(listeners);
                                bean.off(root, listeners);
                                bean.off(videoTag, listeners);
                                common.removeNode(videoTag);
                                videoTag = 0;
                            }
                        }
                    };

                // pre 6.0.4: no boolean api.conf.poster and no poster with autoplay
                if (/^6\.0\.[0-3]$/.test(version) &&
                        !player.conf.splash && !player.conf.poster && !player.conf.autoplay) {
                    bc = common.css(root, 'backgroundColor');
                    // spaces in rgba arg mandatory for recognition
                    has_bg = common.css(root, 'backgroundImage') !== "none" ||
                            (bc && bc !== "rgba(0, 0, 0, 0)" && bc !== "transparent");
                    if (has_bg) {
                        player.conf.poster = true;
                    }
                }

                return engine;
            };

        if (mse && typeof mse.isTypeSupported === "function" && version.indexOf("5.") !== 0) {
            // only load engine if it can be used
            engineImpl.engineName = engineName; // must be exposed
            engineImpl.canPlay = function (type, conf) {
                /*
                  WARNING: MediaSource.isTypeSupported very inconsistent!
                  e.g. Safari ignores codecs entirely, even bogus, like codecs="XYZ"
                  example avc3 main level 3.1 + aac_he: avc3.4d401f; mp4a.40.5
                  example avc1 high level 4.1 + aac_lc: avc1.640029; mp4a.40.2
                  default: avc1 baseline level 3.0 + aac_lc
                */
                // inject dash conf at earliest opportunity
                dashconf = extend({
                    type: "video/mp4",
                    codecs: "avc1.42c01e,mp4a.40.2",
                    qualitiesForSafari: true
                }, conf[engineName], conf.clip[engineName]);

                return dashCanPlay(type, dashconf.type, dashconf.codecs);
            };

            // put on top of engine stack
            // so mpegedash is tested before html5
            flowplayer.engines.unshift(engineImpl);

        }

    };
    if (typeof module === 'object' && module.exports) {
        module.exports = extension.bind(undefined, require('dashjs'));
    } else if (window.dashjs && window.flowplayer) {
        extension(window.dashjs, window.flowplayer);
    }
}());
<commit_msg>Support encrypted streams via new 'protection' clip option (#3)<commit_after>/*jslint browser: true, for: true */
/*global dashjs, flowplayer, MediaPlayer, window */

/*!

   MPEG-DASH engine plugin for Flowplayer HTML5

   Copyright (c) 2015-2017, Flowplayer Drive Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes hls.js
   Copyright (c) 2015 Dailymotion (http://www.dailymotion.com)
   https://github.com/dailymotion/hls.js/blob/master/LICENSE

   Requires Flowplayer HTML5 version 6.x
   $GIT_DESC$

*/
(function () {
    "use strict";
    var extension = function (dashjs, flowplayer) {
        var engineName = "dash",
            mse = window.MediaSource || window.WebKitMediaSource,
            common = flowplayer.common,
            extend = flowplayer.extend,
            version = flowplayer.version,
            coreV6 = version.indexOf("6.") === 0,
            dashconf,

            dashCanPlay = function (sourceType, dashType, dashCodecs) {
                return sourceType.toLowerCase() === "application/dash+xml" &&
                        mse.isTypeSupported(dashType + ';codecs="' + dashCodecs + '"') &&
                        // Android MSE advertises he-aac, but fails
                        (dashCodecs.indexOf("mp4a.40.5") < 0 || navigator.userAgent.indexOf("Android") < 0);
            },

            engineImpl = function dashjsEngine(player, root) {
                var bean = flowplayer.bean,
                    support = flowplayer.support,
                    mediaPlayer,
                    videoTag,
                    handleError = function (errorCode, src, url) {
                        var errobj = {code: errorCode};

                        if (errorCode > 2) {
                            errobj.video = extend(player.video, {
                                src: src,
                                url: url || src
                            });
                        }
                        return errobj;
                    },

                    lastSelectedQuality = -1,
                    initQualitySelection = function (dashQualitiesConf, data) {
                        // multiperiod not supported
                        if (!dashQualitiesConf || !support.inlineVideo ||
                                data.Period_asArray.length > 1 ||
                                (support.browser.safari && !dashconf.qualitiesForSafari)) {
                            return;
                        }

                        var vsets = [],
                            bandwidths = [],
                            qualities = [],
                            qIndices = [];

                        data.Period_asArray[0].AdaptationSet_asArray.forEach(function (aset) {
                            if (aset.par) {
                                aset.Representation_asArray.forEach(function (repr) {
                                    var codecs = repr.mimeType + ";codecs=" + repr.codecs;

                                    if (mse.isTypeSupported(codecs)) {
                                        bandwidths.push(repr.bandwidth);
                                        vsets.push({
                                            bandwidth: repr.bandwidth,
                                            height: repr.height,
                                            width: repr.width
                                        });
                                    }
                                });
                            }
                        });
                        if (bandwidths.length < 2) {
                            return;
                        }
                        bandwidths.sort(function (a, b) {
                            return a - b;
                        });

                        if (dashQualitiesConf !== true) {
                            if (typeof dashQualitiesConf === "string") {
                                dashQualitiesConf.split(/\s*,\s*/).forEach(function (q) {
                                    qIndices.push(parseInt(q, 10));
                                });
                            } else if (typeof dashQualitiesConf !== "boolean") {
                                dashQualitiesConf.forEach(function (q) {
                                    qIndices.push(isNaN(Number(q))
                                        ? q.level
                                        : q);
                                });
                            }
                        }
                        bandwidths.forEach(function (bw) {
                            var levelIndex = 0;

                            vsets.forEach(function (vset) {
                                if (bw === vset.bandwidth &&
                                        (dashQualitiesConf === true || qIndices.indexOf(levelIndex) > -1)) {
                                    qualities.push(levelIndex);
                                }
                                levelIndex += 1;
                            });
                        });
                        if (qualities.length < 2) {
                            return;
                        }

                        if (dashQualitiesConf === true || qIndices.indexOf(-1) > -1) {
                            qualities.unshift(-1);
                            bandwidths.unshift(0);
                        }

                        player.video.qualities = [];
                        qualities.forEach(function (idx) {
                            var level = vsets[idx],
                                q = qIndices.length
                                    ? dashQualitiesConf[qIndices.indexOf(idx)]
                                    : idx,
                                label = q.label || (idx < 0
                                    ? "Auto"
                                    : level.width + "x" + level.height +
                                            " (" + Math.round(level.bandwidth / 1000) + "k)");

                            player.video.qualities.push({value: idx, label: label});
                        });

                        if (qualities.indexOf(lastSelectedQuality) > -1) {
                            mediaPlayer.setAutoSwitchQualityFor("video", lastSelectedQuality < 0);
                            if (lastSelectedQuality > -1) {
                                mediaPlayer.setInitialBitrateFor("video", Math.round(bandwidths[lastSelectedQuality] / 1000));
                            }
                            player.video.quality = lastSelectedQuality;
                        } else {
                            player.video.quality = -1;
                        }
                    },

                    bc,
                    has_bg,

                    engine = {
                        engineName: engineName,

                        pick: function (sources) {
                            var i,
                                source,
                                dashType,
                                dashCodecs;

                            for (i = 0; i < sources.length; i += 1) {
                                source = sources[i];
                                dashType = source.dashType || dashconf.type;
                                dashCodecs = source.dashCodecs || dashconf.codecs;
                                if (dashCanPlay(source.type, dashType, dashCodecs)) {
                                    if (typeof source.src === 'string') {
                                        source.src = common.createAbsoluteUrl(source.src);
                                    }
                                    return source;
                                }
                            }
                        },

                        load: function (video) {
                            var conf = player.conf,
                                dashUpdatedConf = extend(dashconf, conf.dash, video.dash),
                                dashQualitiesConf = video.dashQualities || conf.dashQualities,
                                EVENTS = {
                                    ended: "finish",
                                    loadeddata: "ready",
                                    pause: "pause",
                                    play: "resume",
                                    progress: "buffer",
                                    ratechange: "speed",
                                    seeked: "seek",
                                    timeupdate: "progress",
                                    volumechange: "volume",
                                    error: "error"
                                },
                                DASHEVENTS = dashjs.MediaPlayer.events,
                                autoplay = !!video.autoplay || !!conf.autoplay,
                                posterClass = "is-poster",
                                livestartpos = 0;

                            if (video.dashQualities === false || coreV6) {
                                dashQualitiesConf = false;
                            } else if (dashQualitiesConf === undefined) {
                                dashQualitiesConf = true;
                            }

                            if (!mediaPlayer) {
                                videoTag = common.findDirect("video", root)[0]
                                        || common.find(".fp-player > video", root)[0];

                                if (videoTag) {
                                    // destroy video tag
                                    // otherwise <video autoplay> continues to play
                                    common.find("source", videoTag).forEach(function (source) {
                                        source.removeAttribute("src");
                                    });
                                    videoTag.removeAttribute("src");
                                    videoTag.load();
                                    common.removeNode(videoTag);
                                }

                                // dash.js enforces preload="auto" and
                                // autoplay depending on initialization
                                // so setting the attributes here will have no effect
                                videoTag = common.createElement("video", {
                                    "class": "fp-engine " + engineName + "-engine",
                                    "volume": player.volumeLevel,
                                    "x-webkit-airplay": "allow"
                                });

                                Object.keys(EVENTS).forEach(function (key) {
                                    var flow = EVENTS[key],
                                        type = key + "." + engineName,
                                        arg;

                                    bean.on(videoTag, type, function (e) {
                                        if (conf.debug && flow.indexOf("progress") < 0) {
                                            console.log(type, "->", flow, e.originalEvent);
                                        }

                                        var vct = videoTag.currentTime,
                                            ct = (mediaPlayer.time && mediaPlayer.time()) || vct,
                                            seekable = videoTag.seekable,
                                            buffered = videoTag.buffered,
                                            buffer = 0,
                                            buffend = 0,
                                            errorCode,
                                            i;

                                        switch (flow) {
                                        case "ready":
                                            arg = extend(player.video, {
                                                duration: mediaPlayer.duration(),
                                                seekable: seekable.length && seekable.end(null),
                                                width: videoTag.videoWidth,
                                                height: videoTag.videoHeight,
                                                url: player.video.src
                                            });
                                            break;
                                        case "resume":
                                            if (coreV6 && player.poster) {
                                                common.removeClass(root, posterClass);
                                                player.poster = false;
                                            }
                                            break;
                                        case "seek":
                                            arg = ct;
                                            break;
                                        case "progress":
                                            if (player.live && !player.dvr) {
                                                if (!livestartpos && vct) {
                                                    livestartpos = vct;
                                                }
                                                arg = livestartpos
                                                    ? vct - livestartpos
                                                    : 0;
                                            } else {
                                                arg = ct;
                                            }
                                            break;
                                        case "speed":
                                            // dash.js often triggers playback rate changes
                                            // when adapting bit rate
                                            // except when in debug mode, only
                                            // trigger explicit events via speed method
                                            if (!dashUpdatedConf.debug) {
                                                e.preventDefault();
                                                return;
                                            }
                                            arg = videoTag.playbackRate;
                                            break;
                                        case "volume":
                                            arg = videoTag.volume;
                                            break;
                                        case "buffer":
                                            try {
                                                buffer = buffered.length && buffered.end(null);
                                                if (!player.video.live && ct && buffer) {
                                                    // cycle through time ranges to obtain buffer
                                                    // nearest current time
                                                    for (i = buffered.length - 1; i > -1; i -= 1) {
                                                        buffend = buffered.end(i);

                                                        if (buffend >= ct) {
                                                            buffer = buffend;
                                                        }
                                                    }
                                                }
                                            } catch (ignore) {}
                                            video.buffer = buffer;
                                            arg = buffer;
                                            break;
                                        case "error":
                                            errorCode = videoTag.error && videoTag.error.code;
                                            arg = handleError(errorCode, player.video.src);
                                            break;
                                        }

                                        player.trigger(flow, [player, arg]);
                                    });
                                });

                                player.on("error." + engineName, function () {
                                    if (mediaPlayer) {
                                        player.engine.unload();
                                    }
                                });

                                if (!coreV6) {
                                    player.on("quality." + engineName, function (e, api, q) {
                                        mediaPlayer.setAutoSwitchQualityFor("video", q < 0);
                                        if (q > -1) {
                                            mediaPlayer.setQualityFor("video", q);
                                        }
                                        lastSelectedQuality = q;
                                    });

                                } else if (conf.poster) {
                                    var posterHack = function (e) {
                                        if (e.type === "stop" || !autoplay) {
                                            setTimeout(function () {
                                                if (!player.poster) {
                                                    common.addClass(root, posterClass);
                                                    player.poster = true;
                                                }
                                            });
                                        }
                                    };

                                    player.one("ready." + engineName, posterHack).on("stop." + engineName, posterHack);
                                }

                                common.prepend(common.find(".fp-player", root)[0], videoTag);

                            } else {
                                mediaPlayer.reset();
                            }

                            mediaPlayer = dashjs.MediaPlayer().create();
                            player.engine[engineName] = mediaPlayer;

                            if (video.dash && video.dash.protection) {
                                mediaPlayer.setProtectionData(video.dash.protection);
                            }
                            // new ABR algo
                            mediaPlayer.enableBufferOccupancyABR(dashUpdatedConf.bufferOccupancyABR);
                            // caching can cause failures in playlists
                            // for the moment disable entirely
                            mediaPlayer.enableLastBitrateCaching(false);
                            // for seeking in paused state
                            mediaPlayer.setScheduleWhilePaused(true);
                            mediaPlayer.getDebug().setLogToBrowserConsole(dashUpdatedConf.debug);

                            Object.keys(DASHEVENTS).forEach(function (key) {
                                var etype = DASHEVENTS[key],
                                    fpEventType = engineName + etype.charAt(0).toUpperCase() + etype.slice(1),
                                    listeners = dashUpdatedConf.listeners,
                                    expose = listeners && listeners.indexOf(fpEventType) > -1;

                                mediaPlayer.on(etype, function (e) {
                                    var src = player.video.src,
                                        videoDashConf = player.video.dash,
                                        fperr,
                                        errobj;

                                    switch (key) {
                                    case "MANIFEST_LOADED":
                                        initQualitySelection(dashQualitiesConf, e.data);
                                        break;
                                    case "ERROR":
                                        switch (e.error) {
                                        case "download":
                                            fperr = 4;
                                            break;
                                        case "manifestError":
                                            if (videoDashConf && videoDashConf.protection) {
                                                return;
                                            }
                                            fperr = 5;
                                            break;
                                        case "mediasource":
                                            switch (e.event) {
                                            case "MEDIA_ERR_DECODE":
                                                fperr = 3;
                                                break;
                                            case "MEDIA_ERR_SRC_NOT_SUPPORTED":
                                                fperr = 5;
                                                break;
                                            case "MEDIA_ERR_NETWORK":
                                                fperr = 2;
                                                break;
                                            case "MEDIA_ERR_ABORTED":
                                                fperr = 1;
                                                break;
                                            }
                                            break;
                                        default:
                                            fperr = 5;
                                        }
                                        errobj = handleError(fperr, src, e.event.url);
                                        player.trigger('error', [player, errobj]);
                                        break;
                                    }

                                    if (expose) {
                                        player.trigger(fpEventType, [player, e]);
                                    }
                                });
                            });

                            mediaPlayer.initialize(videoTag, video.src, autoplay);

                            // update video object before ready
                            player.video = video;

                            if (player.paused && autoplay) {
                                if (support.firstframe) {
                                    mediaPlayer.play();
                                } else {
                                    videoTag.play();
                                }
                            }
                        },

                        resume: function () {
                            mediaPlayer.play();
                        },

                        pause: function () {
                            mediaPlayer.pause();
                        },

                        seek: function (time) {
                            mediaPlayer.seek(time);
                        },

                        volume: function (level) {
                            if (videoTag) {
                                videoTag.volume = level;
                            }
                        },

                        speed: function (val) {
                            videoTag.playbackRate = val;
                            // see ratechange/speed event
                            player.trigger('speed', [player, val]);
                        },

                        unload: function () {
                            if (mediaPlayer) {
                                var listeners = "." + engineName;

                                mediaPlayer.reset();
                                mediaPlayer = 0;
                                player.off(listeners);
                                bean.off(root, listeners);
                                bean.off(videoTag, listeners);
                                common.removeNode(videoTag);
                                videoTag = 0;
                            }
                        }
                    };

                // pre 6.0.4: no boolean api.conf.poster and no poster with autoplay
                if (/^6\.0\.[0-3]$/.test(version) &&
                        !player.conf.splash && !player.conf.poster && !player.conf.autoplay) {
                    bc = common.css(root, 'backgroundColor');
                    // spaces in rgba arg mandatory for recognition
                    has_bg = common.css(root, 'backgroundImage') !== "none" ||
                            (bc && bc !== "rgba(0, 0, 0, 0)" && bc !== "transparent");
                    if (has_bg) {
                        player.conf.poster = true;
                    }
                }

                return engine;
            };

        if (mse && typeof mse.isTypeSupported === "function" && version.indexOf("5.") !== 0) {
            // only load engine if it can be used
            engineImpl.engineName = engineName; // must be exposed
            engineImpl.canPlay = function (type, conf) {
                /*
                  WARNING: MediaSource.isTypeSupported very inconsistent!
                  e.g. Safari ignores codecs entirely, even bogus, like codecs="XYZ"
                  example avc3 main level 3.1 + aac_he: avc3.4d401f; mp4a.40.5
                  example avc1 high level 4.1 + aac_lc: avc1.640029; mp4a.40.2
                  default: avc1 baseline level 3.0 + aac_lc
                */
                // inject dash conf at earliest opportunity
                dashconf = extend({
                    type: "video/mp4",
                    codecs: "avc1.42c01e,mp4a.40.2",
                    qualitiesForSafari: true
                }, conf[engineName], conf.clip[engineName]);

                return dashCanPlay(type, dashconf.type, dashconf.codecs);
            };

            // put on top of engine stack
            // so mpegedash is tested before html5
            flowplayer.engines.unshift(engineImpl);

        }

    };
    if (typeof module === 'object' && module.exports) {
        module.exports = extension.bind(undefined, require('dashjs'));
    } else if (window.dashjs && window.flowplayer) {
        extension(window.dashjs, window.flowplayer);
    }
}());
<|endoftext|>


<commit_before>const exec = require('child_process').exec;

const AUDIT_EXCEPTIONS = [
  'tar',
  'trim-newlines',
  'css-what',
  'normalize-url',
  'diff',
  'shelljs',
  // no patches available
  'ansi-html',
  'minimist',
  'moment',
  'axios',
  'async',
  'nth-check',
  'semver-regex',
  'scss-tokenizer',
  // no package found
  'codecov',
  'scss-tokenizer'
];
const execute = (command, callback) => {
  exec(
    command,
    {
      maxBuffer: 2000 * 4096
    },
    (error, stdout, stderr) => {
      callback(stdout);
    }
  );
};
execute('npm audit --json', json => {
  const advisories = JSON.parse(json).advisories;
  if (!advisories) {
    console.info('Most likely npm audit is unavailable', json);
    process.exit(0);
  }
  let auditPass = true;
  for (const id in advisories) {
    if (
      advisories[id].severity === 'high' &&
      !AUDIT_EXCEPTIONS.includes(advisories[id].module_name)
    ) {
      console.error('AUDIT Failed', advisories[id]);
      auditPass = false;
    }
  }
  if (!auditPass) process.exit(1);
});
<commit_msg>chore: 👷 add exceptions<commit_after>const exec = require('child_process').exec;

const AUDIT_EXCEPTIONS = [
  'tar',
  'trim-newlines',
  'css-what',
  'normalize-url',
  'diff',
  'shelljs',
  // no patches available
  'ansi-html',
  'minimist',
  'moment',
  'axios',
  'async',
  'nth-check',
  'semver-regex',
  'scss-tokenizer',
  // no package found
  'codecov',
  'scss-tokenizer',
  '@openzeppelin/contracts',
  'terser'
];
const execute = (command, callback) => {
  exec(
    command,
    {
      maxBuffer: 2000 * 4096
    },
    (error, stdout, stderr) => {
      callback(stdout);
    }
  );
};
execute('npm audit --json', json => {
  const advisories = JSON.parse(json).advisories;
  if (!advisories) {
    console.info('Most likely npm audit is unavailable', json);
    process.exit(0);
  }
  let auditPass = true;
  for (const id in advisories) {
    if (
      advisories[id].severity === 'high' &&
      !AUDIT_EXCEPTIONS.includes(advisories[id].module_name)
    ) {
      console.error('AUDIT Failed', advisories[id]);
      auditPass = false;
    }
  }
  if (!auditPass) process.exit(1);
});
<|endoftext|>


<commit_before><commit_msg>Update webdis.js<commit_after><|endoftext|>


<commit_before>// generated on 2017-01-27 using generator-webapp 2.3.2
const gulp = require('gulp');
const gulpLoadPlugins = require('gulp-load-plugins');
const browserSync = require('browser-sync').create();
const del = require('del');
const wiredep = require('wiredep').stream;
const runSequence = require('run-sequence');
const browserify = require('browserify');
const babelify = require('babelify');
const browserifyOptional = require('browserify-optional');
const buffer = require('vinyl-buffer');
const source = require('vinyl-source-stream');
const rename = require('gulp-rename');
const es = require('event-stream');
const fs = require('fs');
const lazypipe = require('lazypipe')

const $ = gulpLoadPlugins();
const reload = browserSync.reload;

let dev = true;

gulp.task('styles', () => {
  return gulp.src('app/styles/*.css')
    .pipe($.sourcemaps.init())
    .pipe($.autoprefixer({ browsers: ['> 1%', 'last 2 versions', 'Firefox ESR'] }))
    .pipe($.sourcemaps.write())
    .pipe(gulp.dest('.tmp/styles'))
    .pipe(reload({ stream: true }));
});

gulp.task('scripts', () => {
  const files = [
    'app/scripts/main.js',
    'app/scripts/examples.js',
  ];

  const tasks = files.map((entry) => {
    return browserify({
      entries: [entry],
      transform: [babelify, browserifyOptional],
      debug: true,
    })
    .bundle()
    .pipe(source(entry.substr(entry.lastIndexOf('/') + 1)))
    .pipe(rename({
      extname: '.bundle.js',
    }))
    .pipe($.plumber())
    // .pipe($.sourcemaps.init())
    // .pipe($.babel())
    .pipe(buffer())
    .pipe($.sourcemaps.init({ loadMaps: true }))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest('.tmp/scripts'))
    .pipe(reload({ stream: true }));
  });
  return es.merge.apply(null, tasks);
});

function lint(files, options) {
  return gulp.src(files)
    .pipe($.eslint({ fix: true }))
    .pipe(reload({ stream: true, once: true }))
    .pipe($.eslint.format())
    .pipe($.if(!browserSync.active, $.eslint.failAfterError()));
}

gulp.task('lint', () => {
  return lint('app/scripts/**/*.js');
});
gulp.task('lint:test', () => {
  return lint('test/spec/**/*.js')
    .pipe(gulp.dest('test/spec'));
});

gulp.task('html', ['styles', 'scripts'], () => {
  return gulp.src('app/*.html')
    // We want to have sourcemaps generated for the bundled scripts come through useref and uglification
    // We need lazypipe because useref internally needs multiple copies of this pipeline.
    .pipe($.useref({searchPath: ['.tmp', 'app', '.']}, lazypipe().pipe($.sourcemaps.init, { loadMaps: true })))
    .pipe($.if('*.js', $.uglify()))
    .pipe($.if('*.css', $.cssnano({safe: true, autoprefixer: false})))
    .pipe($.if('*.html', $.htmlmin({collapseWhitespace: true})))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest('dist'));
});

gulp.task('images', () => {
  return gulp.src('app/images/**/*')
    .pipe($.cache($.imagemin()))
    .pipe(gulp.dest('dist/images'));
});

gulp.task('fonts', () => {
  return gulp.src(require('main-bower-files')('**/*.{eot,svg,ttf,woff,woff2}', function (err) {})
    .concat('app/fonts/**/*'))
    .pipe($.if(dev, gulp.dest('.tmp/fonts'), gulp.dest('dist/fonts')));
});

gulp.task('copyStuff', () => {
  // copy bootstrap fonts
  gulp.src('./bower_components/bootstrap/fonts/*.{ttf,woff,woff2,eot,svg}')
    .pipe(gulp.dest('dist/fonts'));

  // copy files in exampeData directory
  // gulp.src('app/exampleData/*')
    // .pipe(gulp.dest('dist/exampleData'));
});

gulp.task('extras', () => {
  return gulp.src([
    'app/*',
    '!app/*.html'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'));
});

gulp.task('clean', del.bind(null, ['.tmp', 'dist']));

gulp.task('serve', () => {
  runSequence(['clean', 'wiredep'], ['styles', 'scripts', 'fonts'], () => {
    browserSync.init({
      notify: false,
      port: 9000,
      // Turn off BrowserSync cross-device syncing and UI
      // TODO: Protect these with authorization when running on a multi-user Internet-visible system
      ghostMode: false,
      ui: false,
      server: {
        baseDir: ['.tmp', 'app'],
        routes: {
          '/bower_components': 'bower_components'
        }
      }
    });

    gulp.watch([
      'app/*.html',
      'app/images/**/*',
      '.tmp/fonts/**/*'
    ]).on('change', reload);

    gulp.watch('app/styles/**/*.css', ['styles']);
    gulp.watch('app/scripts/**/*.js', ['scripts']);
    gulp.watch('app/fonts/**/*', ['fonts']);
    gulp.watch('bower.json', ['wiredep', 'fonts']);
  });
});

gulp.task('serve:dist', ['default'], () => {
  browserSync.init({
    notify: false,
    port: 9000,
    // Turn off BrowserSync cross-device syncing and UI
    // TODO: Protect these with authorization when running on a multi-user Internet-visible system
    ghostMode: false,
    ui: false,
    server: {
      baseDir: ['dist']
    }
  });
});

gulp.task('serve:test', ['scripts'], () => {
  browserSync.init({
    notify: false,
    port: 9000,
    // Turn off BrowserSync cross-device syncing and UI
    // TODO: Protect these with authorization when running on a multi-user Internet-visible system
    ghostMode: false,
    ui: false,
    server: {
      baseDir: 'test',
      routes: {
        '/scripts': '.tmp/scripts',
        '/bower_components': 'bower_components'
      }
    }
  });

  gulp.watch('app/scripts/**/*.js', ['scripts']);
  gulp.watch(['test/spec/**/*.js', 'test/index.html']).on('change', reload);
  gulp.watch('test/spec/**/*.js', ['lint:test']);
});

// inject bower components
gulp.task('wiredep', () => {
  gulp.src('app/*.html')
    .pipe(wiredep({
      exclude: ['bootstrap.js'],
      ignorePath: /^(\.\.\/)*\.\./
    }))
    .pipe(gulp.dest('app'));
});

gulp.task('build', ['lint', 'html', 'images', 'fonts', 'extras', 'copyStuff'], () => {
  return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
});

gulp.task('deploy', ['default'], () => {
  return gulp.src('dist/**/*')
    .pipe($.ghPages());
});

gulp.task('default', () => {
  return new Promise(resolve => {
    dev = false;
    runSequence(['clean', 'wiredep'], 'build', resolve);
  });
});
<commit_msg>Move buffering to fix #56<commit_after>// generated on 2017-01-27 using generator-webapp 2.3.2
const gulp = require('gulp');
const gulpLoadPlugins = require('gulp-load-plugins');
const browserSync = require('browser-sync').create();
const del = require('del');
const wiredep = require('wiredep').stream;
const runSequence = require('run-sequence');
const browserify = require('browserify');
const babelify = require('babelify');
const browserifyOptional = require('browserify-optional');
const buffer = require('vinyl-buffer');
const source = require('vinyl-source-stream');
const rename = require('gulp-rename');
const es = require('event-stream');
const fs = require('fs');
const lazypipe = require('lazypipe')

const $ = gulpLoadPlugins();
const reload = browserSync.reload;

let dev = true;

gulp.task('styles', () => {
  return gulp.src('app/styles/*.css')
    .pipe($.sourcemaps.init())
    .pipe($.autoprefixer({ browsers: ['> 1%', 'last 2 versions', 'Firefox ESR'] }))
    .pipe($.sourcemaps.write())
    .pipe(gulp.dest('.tmp/styles'))
    .pipe(reload({ stream: true }));
});

gulp.task('scripts', () => {
  const files = [
    'app/scripts/main.js',
    'app/scripts/examples.js',
  ];

  const tasks = files.map((entry) => {
    return browserify({
      entries: [entry],
      transform: [babelify, browserifyOptional],
      debug: true,
    })
    .bundle()
    .pipe(source(entry.substr(entry.lastIndexOf('/') + 1)))
    .pipe(buffer())
    .pipe(rename({
      extname: '.bundle.js',
    }))
    .pipe($.plumber())
    // .pipe($.sourcemaps.init())
    // .pipe($.babel())
    .pipe($.sourcemaps.init({ loadMaps: true }))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest('.tmp/scripts'))
    .pipe(reload({ stream: true }));
  });
  return es.merge.apply(null, tasks);
});

function lint(files, options) {
  return gulp.src(files)
    .pipe($.eslint({ fix: true }))
    .pipe(reload({ stream: true, once: true }))
    .pipe($.eslint.format())
    .pipe($.if(!browserSync.active, $.eslint.failAfterError()));
}

gulp.task('lint', () => {
  return lint('app/scripts/**/*.js');
});
gulp.task('lint:test', () => {
  return lint('test/spec/**/*.js')
    .pipe(gulp.dest('test/spec'));
});

gulp.task('html', ['styles', 'scripts'], () => {
  return gulp.src('app/*.html')
    // We want to have sourcemaps generated for the bundled scripts come through useref and uglification
    // We need lazypipe because useref internally needs multiple copies of this pipeline.
    .pipe($.useref({searchPath: ['.tmp', 'app', '.']}, lazypipe().pipe($.sourcemaps.init, { loadMaps: true })))
    .pipe($.if('*.js', $.uglify()))
    .pipe($.if('*.css', $.cssnano({safe: true, autoprefixer: false})))
    .pipe($.if('*.html', $.htmlmin({collapseWhitespace: true})))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest('dist'));
});

gulp.task('images', () => {
  return gulp.src('app/images/**/*')
    .pipe($.cache($.imagemin()))
    .pipe(gulp.dest('dist/images'));
});

gulp.task('fonts', () => {
  return gulp.src(require('main-bower-files')('**/*.{eot,svg,ttf,woff,woff2}', function (err) {})
    .concat('app/fonts/**/*'))
    .pipe($.if(dev, gulp.dest('.tmp/fonts'), gulp.dest('dist/fonts')));
});

gulp.task('copyStuff', () => {
  // copy bootstrap fonts
  gulp.src('./bower_components/bootstrap/fonts/*.{ttf,woff,woff2,eot,svg}')
    .pipe(gulp.dest('dist/fonts'));

  // copy files in exampeData directory
  // gulp.src('app/exampleData/*')
    // .pipe(gulp.dest('dist/exampleData'));
});

gulp.task('extras', () => {
  return gulp.src([
    'app/*',
    '!app/*.html'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'));
});

gulp.task('clean', del.bind(null, ['.tmp', 'dist']));

gulp.task('serve', () => {
  runSequence(['clean', 'wiredep'], ['styles', 'scripts', 'fonts'], () => {
    browserSync.init({
      notify: false,
      port: 9000,
      // Turn off BrowserSync cross-device syncing and UI
      // TODO: Protect these with authorization when running on a multi-user Internet-visible system
      ghostMode: false,
      ui: false,
      server: {
        baseDir: ['.tmp', 'app'],
        routes: {
          '/bower_components': 'bower_components'
        }
      }
    });

    gulp.watch([
      'app/*.html',
      'app/images/**/*',
      '.tmp/fonts/**/*'
    ]).on('change', reload);

    gulp.watch('app/styles/**/*.css', ['styles']);
    gulp.watch('app/scripts/**/*.js', ['scripts']);
    gulp.watch('app/fonts/**/*', ['fonts']);
    gulp.watch('bower.json', ['wiredep', 'fonts']);
  });
});

gulp.task('serve:dist', ['default'], () => {
  browserSync.init({
    notify: false,
    port: 9000,
    // Turn off BrowserSync cross-device syncing and UI
    // TODO: Protect these with authorization when running on a multi-user Internet-visible system
    ghostMode: false,
    ui: false,
    server: {
      baseDir: ['dist']
    }
  });
});

gulp.task('serve:test', ['scripts'], () => {
  browserSync.init({
    notify: false,
    port: 9000,
    // Turn off BrowserSync cross-device syncing and UI
    // TODO: Protect these with authorization when running on a multi-user Internet-visible system
    ghostMode: false,
    ui: false,
    server: {
      baseDir: 'test',
      routes: {
        '/scripts': '.tmp/scripts',
        '/bower_components': 'bower_components'
      }
    }
  });

  gulp.watch('app/scripts/**/*.js', ['scripts']);
  gulp.watch(['test/spec/**/*.js', 'test/index.html']).on('change', reload);
  gulp.watch('test/spec/**/*.js', ['lint:test']);
});

// inject bower components
gulp.task('wiredep', () => {
  gulp.src('app/*.html')
    .pipe(wiredep({
      exclude: ['bootstrap.js'],
      ignorePath: /^(\.\.\/)*\.\./
    }))
    .pipe(gulp.dest('app'));
});

gulp.task('build', ['lint', 'html', 'images', 'fonts', 'extras', 'copyStuff'], () => {
  return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
});

gulp.task('deploy', ['default'], () => {
  return gulp.src('dist/**/*')
    .pipe($.ghPages());
});

gulp.task('default', () => {
  return new Promise(resolve => {
    dev = false;
    runSequence(['clean', 'wiredep'], 'build', resolve);
  });
});
<|endoftext|>


<commit_before>(function () {
'use strict';

Vue.config.debug = true;

window.Guacamoly = window.Guacamoly || {};
var Core = window.Guacamoly.Core;

// https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
function markdownTargetBlank(md) {
    // stash the default renderer
    var defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
        return self.renderToken(tokens, idx, options);
    };

    md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
        var href = tokens[idx].attrs[tokens[idx].attrIndex('href')][1];

        if (href.indexOf('https://') === 0 || href.indexOf('http://') === 0) {
            // in case another plugin added that attribute already
            var aIndex = tokens[idx].attrIndex('target');

            if (aIndex < 0) {
                tokens[idx].attrPush(['target', '_blank']); // add new attribute
            } else {
                tokens[idx].attrs[aIndex][1] = '_blank';    // replace value of existing attr
            }
        }

        return defaultRender(tokens, idx, options, env, self);
    };
}

function colorizeIt(md, options) {
    var regexp = /\:([#\w\-]+)\:/;

    function isColor(color) {
        // https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
        var colors = [
            'clear',
            'aliceblue', 'lightsalmon', 'antiquewhite', 'lightseagreen', 'aqua', 'lightskyblue', 'aquamarine', 'lightslategray', 'azure', 'lightsteelblue', 'beige', 'lightyellow', 'bisque', 'lime', 'black', 'limegreen', 'blanchedalmond', 'linen', 'blue', 'magenta', 'blueviolet', 'maroon', 'brown', 'mediumaquamarine', 'burlywood', 'mediumblue', 'cadetblue', 'mediumorchid', 'chartreuse', 'mediumpurple', 'chocolate', 'mediumseagreen', 'coral', 'mediumslateblue', 'cornflowerblue', 'mediumspringgreen', 'cornsilk', 'mediumturquoise', 'crimson', 'mediumvioletred', 'cyan', 'midnightblue', 'darkblue', 'mintcream', 'darkcyan', 'mistyrose', 'darkgoldenrod', 'moccasin', 'darkgray', 'navajowhite', 'darkgreen', 'navy', 'darkkhaki', 'oldlace', 'darkmagenta', 'olive', 'darkolivegreen', 'olivedrab', 'darkorange', 'orange', 'darkorchid', 'orangered', 'darkred', 'orchid', 'darksalmon', 'palegoldenrod', 'darkseagreen', 'palegreen', 'darkslateblue', 'paleturquoise', 'darkslategray', 'palevioletred', 'darkturquoise', 'papayawhip', 'darkviolet', 'peachpuff', 'deeppink', 'peru', 'deepskyblue', 'pink', 'dimgray', 'plum', 'dodgerblue', 'powderblue', 'firebrick', 'purple', 'floralwhite', 'red', 'forestgreen', 'rosybrown', 'fuchsia', 'royalblue', 'gainsboro', 'saddlebrown', 'ghostwhite', 'salmon', 'gold', 'sandybrown', 'goldenrod', 'seagreen', 'gray', 'seashell', 'green', 'sienna', 'greenyellow', 'silver', 'honeydew', 'skyblue', 'hotpink', 'slateblue', 'indianred', 'slategray', 'indigo', 'snow', 'ivory', 'springgreen', 'khaki', 'steelblue', 'lavender', 'tan', 'lavenderblush', 'teal', 'lawngreen', 'thistle', 'lemonchiffon', 'tomato', 'lightblue', 'turquoise', 'lightcoral', 'violet', 'lightcyan', 'wheat', 'lightgoldenrodyellow', 'white', 'lightgreen', 'whitesmoke', 'lightgrey', 'yellow', 'lightpink', 'yellowgreen'
        ];

        if (color[0] === '#') return true;

        return colors.indexOf(color) !== -1;
    }

    md.inline.ruler.push('colorizeIt', function (state, silent) {
        // slowwww... maybe use an advanced regexp engine for this
        var match = regexp.exec(state.src.slice(state.pos));
        if (!match) return false;
        if (!isColor(match[1])) return false;

        // valid match found, now we need to advance cursor
        state.pos += match[0].length;

        // don't insert any tokens in silent mode
        if (silent) return true;

        var token = state.push('colorizeIt', '', 0);
        token.meta = { color: match[1] };

        return true;
    });

    md.renderer.rules['colorizeIt'] = function (tokens, id, options, env) {
        if (tokens[id].meta.color === 'clear') {
            return '';
        } else {
            return '';
        }
    };
}

var md = window.markdownit({
    breaks: true,
    html: true,
    linkify: true
}).use(window.markdownitEmoji)
.use(colorizeIt)
.use(window.markdownitCheckbox)
.use(markdownTargetBlank);

md.renderer.rules.emoji = function(token, idx) {
    return twemoji.parse(token[idx].content);
};

Vue.filter('markdown', function (value) {
    if (!value) return '';

    return md.render(value);
});

Vue.filter('prettyDateOffset', function (time) {
    var date = new Date(time),
        diff = (((new Date()).getTime() - date.getTime()) / 1000),
        day_diff = Math.floor(diff / 86400);

    if (isNaN(day_diff) || day_diff < 0)
        return;

    return day_diff === 0 && (
            diff < 60 && 'just now' ||
            diff < 120 && '1 minute ago' ||
            diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
            diff < 7200 && '1 hour ago' ||
            diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
        day_diff === 1 && 'Yesterday' ||
        day_diff < 7 && day_diff + ' days ago' ||
        day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago' ||
        day_diff < 365 && Math.round( day_diff / 30 ) +  ' months ago' ||
                          Math.round( day_diff / 365 ) + ' years ago';
});

// Tag propasal filter
Vue.getCurrentSearchWord = function (search, inputElement) {
    var cursorPos = $(inputElement)[0] ? $(inputElement)[0].selectionStart : -1;
    var word = '';

    if (cursorPos === -1) return '';

    for (var i = 0; i < search.length; ++i) {
        // break if we went beyond and we hit a space
        if (i >= cursorPos && (search[i] === ' ' || search[i] === '\n')) break;

        if (search[i] === ' ' || search[i] === '\n') word = '';
        else word += search[i];
    }

    return word;
};

function proposeTags(options, search, inputSelector, requireHash, threshold) {
    var raw = Vue.getCurrentSearchWord(search, $(inputSelector));

    if (requireHash && raw[0] !== '#') return [];

    var word = raw.replace(/^#/, '');

    if (threshold && threshold > word.length) return [];

    return options.filter(function (o) {
        return o.name.indexOf(word) >= 0;
    });
}

Vue.filter('proposeTags', proposeTags);
Vue.filter('proposeTagsThingsEdit', function (options, search, id) {
    return proposeTags(options, search, $('#textarea-' + id), true, 1);
});

var vue = new Vue({
    el: '#application',
    data: {
        Core: window.Guacamoly.Core,
        tags: [],
        things: [],
        busyAdd: false,
        busyThings: true,
        busyFetchMore: false,
        search: '',
        profile: {},
        mailbox: '',
        settings: {},
        mainView: '',
        thingContent: '',
        thingAttachments: []
    },
    methods: {
        giveAddFocus: function () {
            $('#addTextarea').focus();
        },
        addThing: function () {
            var that = this;

            this.busyAdd = true;

            Core.things.add(this.thingContent, this.thingAttachments, function (error, thing) {
                that.busyAdd = false;

                if (error) return console.error(error);
                that.thingContent = '';
                that.thingAttachments = [];
                that.things.unshift(thing);

                that.refreshTags();
            });
        },
        refreshTags: function (callback) {
            var that = this;

            Core.tags.get(function (error, tags) {
                if (error) return console.error(error);

                that.tags = tags;

                if (callback) callback();
            });
        },
        refresh: function (search) {
            var that = this;

            this.busyThings = true;

            window.location.href = '/#search?' + (search ? encodeURIComponent(search) : '');

            Core.things.get(search || '', function (error, data) {
                if (error) return console.error(error);

                that.things = data;
                that.busyThings = false;
            });
        },
        triggerAttachmentUpload: function () {
            $('#addAttachment').click();
        },
        attachmentChanged: function (event) {
            var that = this;
            var data = new FormData();
            data.append('file', event.target.files[0]);

            this.$root.Core.things.uploadFile(data, function (error, result) {
                if (error) console.error(error);

                that.thingContent += ' [' + result.fileName + '] ';
                that.thingAttachments.push(result);
            });
        },
        activateProposedTag: function (tag) {
            var word = Vue.getCurrentSearchWord(this.thingContent, $('#addTextarea'));
            if (!word) console.log('nothing to add');

            var cursorPosition = $('#addTextarea')[0].selectionStart;

            this.thingContent = this.thingContent.replace(new RegExp(word, 'g'), function (match, offset) {
                return ((cursorPosition - word.length) === offset) ? ('#' + tag.name) : match;
            });

            Vue.nextTick(function () { $('#addTextarea').focus(); });
        },
        main: function () {
            var that = this;

            this.mainView = 'loader';

            Core.session.profile(function (error, profile) {
                if (error) return console.error(error);

                that.profile = profile.user;
                that.mailbox = profile.mailbox;

                Core.settings.get(function (error, settings) {
                    if (error) return console.error(error);

                    // set initial settings
                    that.settings = settings;
                    if (settings.title) window.document.title = settings.title;
                    if (settings.backgroundImageDataUrl) window.document.body.style.backgroundImage = 'url("' + settings.backgroundImageDataUrl + '")';

                    that.refreshTags(function () {
                        that.mainView = 'content';

                        window.setTimeout(function () { $('#searchBarInput').focus(); }, 0);

                        hashChangeHandler();

                        // add global object for browser extensions
                        document.getElementById('guacamoly-settings-node').textContent = JSON.stringify({
                            origin: Core.origin(),
                            token: Core.token(),
                            title: settings.title
                        });
                    });
                });
            });
        }
    },
    ready: function () {
        // Register event handlers
        shortcut.add('Ctrl+s', this.addThing.bind(this), { target: 'addTextarea' });
        shortcut.add('Ctrl+Enter', this.addThing.bind(this), { target: 'addTextarea' });

        this.main();
    }
});

function hashChangeHandler() {
    var action = window.location.hash.split('?')[0];
    var params = window.location.hash.indexOf('?') > 0 ? decodeURIComponent(window.location.hash.slice(window.location.hash.indexOf('?') + 1)) : null;

    if (action === '#search') {
        if (params !== null) vue.search = params;
        vue.refresh(vue.search);
    } else {
        window.location.href = '/#search?';
    }
}

function scrollHandler() {
    // add 1 full pixel to be on the safe side for zoom settings, where pixel values might be floats
    if ($(window).height() + $(window).scrollTop() + 1 >= $(document).height()) {
        // prevent from refetching while in progress
        if (vue.busyFetchMore) return;

        vue.busyFetchMore = true;

        Core.things.fetchMore(function (error, result) {
            vue.busyFetchMore = false;

            if (error) return console.error(error);

            vue.things = vue.things.concat(result);
        });
    }
}

function reset() {
    vue.mainView = 'login';
    vue.things = [];
    vue.tags = [];
    vue.search = '';
    vue.settings = {};
}

Core.onAuthFailure = reset;
Core.onLogout = reset;

Core.settings.onChanged(function (data) {
    vue.settings.title = data.title || 'Guacamoly';
    vue.settings.backgroundImageDataUrl = data.backgroundImageDataUrl;
    vue.settings.wide = data.wide;

    window.document.title = data.title;

    if (data.backgroundImageDataUrl) window.document.body.style.backgroundImage = 'url("' + data.backgroundImageDataUrl + '")';
    else window.document.body.style.backgroundImage = '';
});

Core.things.onDeleted(function (thing) {
    // remove if found
    for (var i = 0; i < vue.things.length; ++i) {
        if (vue.things[i].id === thing.id) {
            vue.things.splice(i, 1);
            return;
        }
    }
});

Core.things.onEdited(function (thing) {
    // move to first spot
    vue.things.splice(0, 0, vue.things.splice(vue.things.indexOf(thing), 1)[0]);
});

window.addEventListener('hashchange', hashChangeHandler, false);
window.addEventListener('scroll', scrollHandler, false);

})();
<commit_msg>Ensure default settings<commit_after>(function () {
'use strict';

Vue.config.debug = true;

window.Guacamoly = window.Guacamoly || {};
var Core = window.Guacamoly.Core;

// https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
function markdownTargetBlank(md) {
    // stash the default renderer
    var defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
        return self.renderToken(tokens, idx, options);
    };

    md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
        var href = tokens[idx].attrs[tokens[idx].attrIndex('href')][1];

        if (href.indexOf('https://') === 0 || href.indexOf('http://') === 0) {
            // in case another plugin added that attribute already
            var aIndex = tokens[idx].attrIndex('target');

            if (aIndex < 0) {
                tokens[idx].attrPush(['target', '_blank']); // add new attribute
            } else {
                tokens[idx].attrs[aIndex][1] = '_blank';    // replace value of existing attr
            }
        }

        return defaultRender(tokens, idx, options, env, self);
    };
}

function colorizeIt(md, options) {
    var regexp = /\:([#\w\-]+)\:/;

    function isColor(color) {
        // https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
        var colors = [
            'clear',
            'aliceblue', 'lightsalmon', 'antiquewhite', 'lightseagreen', 'aqua', 'lightskyblue', 'aquamarine', 'lightslategray', 'azure', 'lightsteelblue', 'beige', 'lightyellow', 'bisque', 'lime', 'black', 'limegreen', 'blanchedalmond', 'linen', 'blue', 'magenta', 'blueviolet', 'maroon', 'brown', 'mediumaquamarine', 'burlywood', 'mediumblue', 'cadetblue', 'mediumorchid', 'chartreuse', 'mediumpurple', 'chocolate', 'mediumseagreen', 'coral', 'mediumslateblue', 'cornflowerblue', 'mediumspringgreen', 'cornsilk', 'mediumturquoise', 'crimson', 'mediumvioletred', 'cyan', 'midnightblue', 'darkblue', 'mintcream', 'darkcyan', 'mistyrose', 'darkgoldenrod', 'moccasin', 'darkgray', 'navajowhite', 'darkgreen', 'navy', 'darkkhaki', 'oldlace', 'darkmagenta', 'olive', 'darkolivegreen', 'olivedrab', 'darkorange', 'orange', 'darkorchid', 'orangered', 'darkred', 'orchid', 'darksalmon', 'palegoldenrod', 'darkseagreen', 'palegreen', 'darkslateblue', 'paleturquoise', 'darkslategray', 'palevioletred', 'darkturquoise', 'papayawhip', 'darkviolet', 'peachpuff', 'deeppink', 'peru', 'deepskyblue', 'pink', 'dimgray', 'plum', 'dodgerblue', 'powderblue', 'firebrick', 'purple', 'floralwhite', 'red', 'forestgreen', 'rosybrown', 'fuchsia', 'royalblue', 'gainsboro', 'saddlebrown', 'ghostwhite', 'salmon', 'gold', 'sandybrown', 'goldenrod', 'seagreen', 'gray', 'seashell', 'green', 'sienna', 'greenyellow', 'silver', 'honeydew', 'skyblue', 'hotpink', 'slateblue', 'indianred', 'slategray', 'indigo', 'snow', 'ivory', 'springgreen', 'khaki', 'steelblue', 'lavender', 'tan', 'lavenderblush', 'teal', 'lawngreen', 'thistle', 'lemonchiffon', 'tomato', 'lightblue', 'turquoise', 'lightcoral', 'violet', 'lightcyan', 'wheat', 'lightgoldenrodyellow', 'white', 'lightgreen', 'whitesmoke', 'lightgrey', 'yellow', 'lightpink', 'yellowgreen'
        ];

        if (color[0] === '#') return true;

        return colors.indexOf(color) !== -1;
    }

    md.inline.ruler.push('colorizeIt', function (state, silent) {
        // slowwww... maybe use an advanced regexp engine for this
        var match = regexp.exec(state.src.slice(state.pos));
        if (!match) return false;
        if (!isColor(match[1])) return false;

        // valid match found, now we need to advance cursor
        state.pos += match[0].length;

        // don't insert any tokens in silent mode
        if (silent) return true;

        var token = state.push('colorizeIt', '', 0);
        token.meta = { color: match[1] };

        return true;
    });

    md.renderer.rules['colorizeIt'] = function (tokens, id, options, env) {
        if (tokens[id].meta.color === 'clear') {
            return '';
        } else {
            return '';
        }
    };
}

var md = window.markdownit({
    breaks: true,
    html: true,
    linkify: true
}).use(window.markdownitEmoji)
.use(colorizeIt)
.use(window.markdownitCheckbox)
.use(markdownTargetBlank);

md.renderer.rules.emoji = function(token, idx) {
    return twemoji.parse(token[idx].content);
};

Vue.filter('markdown', function (value) {
    if (!value) return '';

    return md.render(value);
});

Vue.filter('prettyDateOffset', function (time) {
    var date = new Date(time),
        diff = (((new Date()).getTime() - date.getTime()) / 1000),
        day_diff = Math.floor(diff / 86400);

    if (isNaN(day_diff) || day_diff < 0)
        return;

    return day_diff === 0 && (
            diff < 60 && 'just now' ||
            diff < 120 && '1 minute ago' ||
            diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
            diff < 7200 && '1 hour ago' ||
            diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
        day_diff === 1 && 'Yesterday' ||
        day_diff < 7 && day_diff + ' days ago' ||
        day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago' ||
        day_diff < 365 && Math.round( day_diff / 30 ) +  ' months ago' ||
                          Math.round( day_diff / 365 ) + ' years ago';
});

// Tag propasal filter
Vue.getCurrentSearchWord = function (search, inputElement) {
    var cursorPos = $(inputElement)[0] ? $(inputElement)[0].selectionStart : -1;
    var word = '';

    if (cursorPos === -1) return '';

    for (var i = 0; i < search.length; ++i) {
        // break if we went beyond and we hit a space
        if (i >= cursorPos && (search[i] === ' ' || search[i] === '\n')) break;

        if (search[i] === ' ' || search[i] === '\n') word = '';
        else word += search[i];
    }

    return word;
};

function proposeTags(options, search, inputSelector, requireHash, threshold) {
    var raw = Vue.getCurrentSearchWord(search, $(inputSelector));

    if (requireHash && raw[0] !== '#') return [];

    var word = raw.replace(/^#/, '');

    if (threshold && threshold > word.length) return [];

    return options.filter(function (o) {
        return o.name.indexOf(word) >= 0;
    });
}

Vue.filter('proposeTags', proposeTags);
Vue.filter('proposeTagsThingsEdit', function (options, search, id) {
    return proposeTags(options, search, $('#textarea-' + id), true, 1);
});

var vue = new Vue({
    el: '#application',
    data: {
        Core: window.Guacamoly.Core,
        tags: [],
        things: [],
        busyAdd: false,
        busyThings: true,
        busyFetchMore: false,
        search: '',
        profile: {},
        mailbox: '',
        settings: {},
        mainView: '',
        thingContent: '',
        thingAttachments: []
    },
    methods: {
        giveAddFocus: function () {
            $('#addTextarea').focus();
        },
        addThing: function () {
            var that = this;

            this.busyAdd = true;

            Core.things.add(this.thingContent, this.thingAttachments, function (error, thing) {
                that.busyAdd = false;

                if (error) return console.error(error);
                that.thingContent = '';
                that.thingAttachments = [];
                that.things.unshift(thing);

                that.refreshTags();
            });
        },
        refreshTags: function (callback) {
            var that = this;

            Core.tags.get(function (error, tags) {
                if (error) return console.error(error);

                that.tags = tags;

                if (callback) callback();
            });
        },
        refresh: function (search) {
            var that = this;

            this.busyThings = true;

            window.location.href = '/#search?' + (search ? encodeURIComponent(search) : '');

            Core.things.get(search || '', function (error, data) {
                if (error) return console.error(error);

                that.things = data;
                that.busyThings = false;
            });
        },
        triggerAttachmentUpload: function () {
            $('#addAttachment').click();
        },
        attachmentChanged: function (event) {
            var that = this;
            var data = new FormData();
            data.append('file', event.target.files[0]);

            this.$root.Core.things.uploadFile(data, function (error, result) {
                if (error) console.error(error);

                that.thingContent += ' [' + result.fileName + '] ';
                that.thingAttachments.push(result);
            });
        },
        activateProposedTag: function (tag) {
            var word = Vue.getCurrentSearchWord(this.thingContent, $('#addTextarea'));
            if (!word) console.log('nothing to add');

            var cursorPosition = $('#addTextarea')[0].selectionStart;

            this.thingContent = this.thingContent.replace(new RegExp(word, 'g'), function (match, offset) {
                return ((cursorPosition - word.length) === offset) ? ('#' + tag.name) : match;
            });

            Vue.nextTick(function () { $('#addTextarea').focus(); });
        },
        main: function () {
            var that = this;

            this.mainView = 'loader';

            Core.session.profile(function (error, profile) {
                if (error) return console.error(error);

                that.profile = profile.user;
                that.mailbox = profile.mailbox;

                Core.settings.get(function (error, settings) {
                    if (error) return console.error(error);

                    // set initial settings
                    that.settings = settings;

                    // just ensure we have defaults
                    if (!that.settings.title) that.settings.title = 'Guacamoly';
                    if (!that.settings.wide) that.settings.wide = false;
                    if (!that.settings.backgroundImageDataUrl) that.settings.backgroundImageDataUrl = '';
                    if (!that.settings.keepPositionAfterEdit) that.settings.keepPositionAfterEdit = false;

                    if (settings.title) window.document.title = settings.title;
                    if (settings.backgroundImageDataUrl) window.document.body.style.backgroundImage = 'url("' + settings.backgroundImageDataUrl + '")';

                    that.refreshTags(function () {
                        that.mainView = 'content';

                        window.setTimeout(function () { $('#searchBarInput').focus(); }, 0);

                        hashChangeHandler();

                        // add global object for browser extensions
                        document.getElementById('guacamoly-settings-node').textContent = JSON.stringify({
                            origin: Core.origin(),
                            token: Core.token(),
                            title: settings.title
                        });
                    });
                });
            });
        }
    },
    ready: function () {
        // Register event handlers
        shortcut.add('Ctrl+s', this.addThing.bind(this), { target: 'addTextarea' });
        shortcut.add('Ctrl+Enter', this.addThing.bind(this), { target: 'addTextarea' });

        this.main();
    }
});

function hashChangeHandler() {
    var action = window.location.hash.split('?')[0];
    var params = window.location.hash.indexOf('?') > 0 ? decodeURIComponent(window.location.hash.slice(window.location.hash.indexOf('?') + 1)) : null;

    if (action === '#search') {
        if (params !== null) vue.search = params;
        vue.refresh(vue.search);
    } else {
        window.location.href = '/#search?';
    }
}

function scrollHandler() {
    // add 1 full pixel to be on the safe side for zoom settings, where pixel values might be floats
    if ($(window).height() + $(window).scrollTop() + 1 >= $(document).height()) {
        // prevent from refetching while in progress
        if (vue.busyFetchMore) return;

        vue.busyFetchMore = true;

        Core.things.fetchMore(function (error, result) {
            vue.busyFetchMore = false;

            if (error) return console.error(error);

            vue.things = vue.things.concat(result);
        });
    }
}

function reset() {
    vue.mainView = 'login';
    vue.things = [];
    vue.tags = [];
    vue.search = '';
    vue.settings = {};
}

Core.onAuthFailure = reset;
Core.onLogout = reset;

Core.settings.onChanged(function (data) {
    vue.settings.title = data.title || 'Guacamoly';
    vue.settings.backgroundImageDataUrl = data.backgroundImageDataUrl;
    vue.settings.wide = data.wide;

    window.document.title = data.title;

    if (data.backgroundImageDataUrl) window.document.body.style.backgroundImage = 'url("' + data.backgroundImageDataUrl + '")';
    else window.document.body.style.backgroundImage = '';
});

Core.things.onDeleted(function (thing) {
    // remove if found
    for (var i = 0; i < vue.things.length; ++i) {
        if (vue.things[i].id === thing.id) {
            vue.things.splice(i, 1);
            return;
        }
    }
});

Core.things.onEdited(function (thing) {
    // move to first spot
    vue.things.splice(0, 0, vue.things.splice(vue.things.indexOf(thing), 1)[0]);
});

window.addEventListener('hashchange', hashChangeHandler, false);
window.addEventListener('scroll', scrollHandler, false);

})();
<|endoftext|>


<commit_before>import _ from 'lodash'
import Promise from 'bluebird'
import findMatching from 'bipartite-matching'

// takes an Array of people represented as
//   [{name: String, skills: [String], interests: [String]}]
//
// and computes a pairing, represented as an Object with:
//   {
//     isComplete: true|false, // was everyone paired?
//     pairs: [ // list of pairs
//       {
//         teacherIndex: Int,
//         learnerIndex: Int,
//         skills: [String]},
//         teacherName: String,
//         learnerName: String,
//       }
//     ]
//  }
export const generatePairing = (people) => {
  return new Promise((resolve, reject) => {
    people = _.shuffle(people)
    let teachersCount = 0
    let learnersCount = 0
    const edges = []
    people.forEach((p1, p1Index) => {
      // update counters
      if (p1.skills.length) teachersCount += 1
      if (p1.interests.length) learnersCount += 1
      // for every other person, add an edge [p1, p2] to the
      // graph if p1 can teach a skill to p2
      people.forEach((p2, p2Index) => {
        if (p1Index != p2Index &&
            _.intersection(p1.skills, p2.interests).length) {
          edges.push([p1Index, p2Index])
        }
      })
    })
    console.log(teachersCount, 'teachers')
    console.log(learnersCount, 'learners')
    // find max bipartite matching
    const match = findMatching(people.length, people.length, edges)
    console.log(match.length, 'pairs')
    // return pairing object
    const pairs = match.map((pair) => {
      const teacherIndex = pair[0], learnerIndex = pair[1]
      const teacher = people[teacherIndex], learner = people[learnerIndex]
      const skills = _.intersection(teacher.skills, learner.interests)
      return {
        // indexes of teacher and learner in the people array
        teacherIndex,
        learnerIndex,
        // skills that could be taught by teacher to learner
        skills,
        // names added for convenience
        teacherName: teacher.name,
        learnerName: learner.name,
      }
    })
    const dateISO = new Date().toISOString()
    resolve({
      // a unique-enough time-based id, helps us identify the pairing
      id: dateISO + '_' + Math.floor(1000 * Math.random()),
      // ISO8601 creation date
      createdAt: dateISO,
      // a complete pairing = each person is in 2 pairs
      isComplete: (pairs.length == people.length),
      // list of pairs
      pairs,
    })
  })
}
<commit_msg>added main function to pair all people<commit_after>import _ from 'lodash'
import Promise from 'bluebird'
import findMatching from 'bipartite-matching'
import {getAllPeople, savePairing} from '../airtable'

const {env} = process
// constants that can be overridden through environment variables
export const APPLICANTS_TABLE = env.APPLICANTS_TABLE || 'P2PL Applicants'
export const PAIRINGS_TABLE = env.PAIRINGS_TABLE || 'Pairings'

// Main pairing function:
// - reads all applicants from Airtable
// - creates a pairing
// - saves the pairing into Airtable and returns it
// Different tables can be set up through options
export const pairAllApplicants = (opts = {}) => {
  const aTable = opts.applicantsTable || APPLICANTS_TABLE
  const pTable = opts.pairingsTable || PAIRINGS_TABLE
  console.log(`Pairing people in ${aTable}, saving in ${pTable}`)
  return getAllPeople(aTable)
    .then((people) => {
      console.log(`Found ${people.length} people in ${aTable}`)
      return generatePairing(people)
    })
    .then((pairing) => {
      console.log(`Saving ${pairing.pairs.length} pairs to ${pTable}, id=${pairing.id}`)
      return savePairing(pTable, pairing)
    })
}

// takes an Array of people represented as
//   [{name: String, skills: [String], interests: [String]}]
//
// and computes a pairing, represented as an Object with:
//   {
//     isComplete: true|false, // was everyone paired?
//     pairs: [ // list of pairs
//       {
//         teacherIndex: Int,
//         learnerIndex: Int,
//         skills: [String]},
//         teacherName: String,
//         learnerName: String,
//       }
//     ]
//  }
export const generatePairing = (people) => {
  return new Promise((resolve, reject) => {
    people = _.shuffle(people)
    let teachersCount = 0
    let learnersCount = 0
    const edges = []
    people.forEach((p1, p1Index) => {
      // update counters
      if (p1.skills.length) teachersCount += 1
      if (p1.interests.length) learnersCount += 1
      // for every other person, add an edge [p1, p2] to the
      // graph if p1 can teach a skill to p2
      people.forEach((p2, p2Index) => {
        if (p1Index != p2Index &&
            _.intersection(p1.skills, p2.interests).length) {
          edges.push([p1Index, p2Index])
        }
      })
    })
    console.log(teachersCount, 'teachers')
    console.log(learnersCount, 'learners')
    // find max bipartite matching
    const match = findMatching(people.length, people.length, edges)
    console.log(match.length, 'pairs')
    // return pairing object
    const pairs = match.map((pair) => {
      const teacherIndex = pair[0], learnerIndex = pair[1]
      const teacher = people[teacherIndex], learner = people[learnerIndex]
      const skills = _.intersection(teacher.skills, learner.interests)
      return {
        // indexes of teacher and learner in the people array
        teacherIndex,
        learnerIndex,
        // skills that could be taught by teacher to learner
        skills,
        // names added for convenience
        teacherName: teacher.name,
        learnerName: learner.name,
      }
    })
    const dateISO = new Date().toISOString()
    resolve({
      // a unique-enough time-based id, helps us identify the pairing
      id: dateISO + '_' + Math.floor(1000 * Math.random()),
      // ISO8601 creation date
      createdAt: dateISO,
      // a complete pairing = each person is in 2 pairs
      isComplete: (pairs.length == people.length),
      // list of pairs
      pairs,
    })
  })
}
<|endoftext|>