<commit_before>var measure = function (n) {
    var len = n.length;
    var nspaces = (n.match(/ /g) || []).length;
    var nwide   = (n.match(/[WwMm]/g) || []).length;
    var nnarrow = (n.match(/[Iil]/g) || []).length;
    var nnormal = n.length - nspaces - nwide - nnarrow;
    return nnormal + nspaces*0.8 + nnarrow*0.5 + nwide*1.5;
};

var shorten = function (n) {
    var bits = n.split(' ');
    for (var i = 0; i < bits.length-1; i++)
        bits[i] = bits[i].substr(0,1);
    return bits.join('.');
};

var player_pretty_name = function (p) {
    var name = p.card_short;
    if (measure(name) > 10 && p.popular_name.length > 0)
        name = p.popular_name;
    if (measure(name) > 10)
        name = shorten(name);
    return name;
};

function unpack_data (data, out) {
    var teams = {};
    var players = {};
    var playerByShortName = {};
    var playerByLongName = {};
    data.team.forEach(function (team) {
        teams[team.team_id] = team;
        var playerList = team.player || team.squad;
        playerList.forEach(function (player) {
            players[player.player_id] = player;
            playerByShortName[player.card_short] = player;
            playerByLongName[player.card_long] = player;
        });
    });

    var match_status = data.match.match_status;
    if ((!match_status || match_status === "current") && data.match.result !== "0")
        match_status = "complete";

    if (match_status === "current" && !data.live.innings.batting_team_id) {
        match_status = "dormant";
    }

    switch (match_status) {
        case "dormant":
            out.score = data.match.team1_abbreviation.toUpperCase()+" v "+data.match.team2_abbreviation.toUpperCase();

            if (data.live["break"]) {
                out.fact = data.live["break"];
            }

            if (data.match.match_clock && data.match.match_clock !== "") {
                out.lead = "Match starts in "+data.match.match_clock;
            }
            else if (out.fact !== "") {
                out.lead = out.fact;
                out.fact = "";
            }

            if (data.match.toss_decision && data.match.toss_decision !== "" && data.match.toss_decision !== "0") {
                out.striker_name = teams[data.match.toss_winner_team_id].team_short_name+" won toss,";
                out.nonstriker_name = "will "+data.match.toss_decision_name;
            }
            break;

        case "complete": {
            out.score = data.match.team1_abbreviation.toUpperCase()+" v "+data.match.team2_abbreviation.toUpperCase();

            if (data.match.winner_team_id == "0") {
                out.striker_name = "Match drawn";
            }
            else {
                out.striker_name = teams[data.match.winner_team_id].team_short_name+" won by";
                if (data.match.amount_name === "innings") {
                    out.nonstriker_name = "innings and "+data.match.amount+" runs";
                }
                else {
                    out.nonstriker_name = data.match.amount+" "+data.match.amount_name;
                    if (data.match.amount_balls && data.match.amount_balls > 0) {
                        out.bowler_name = "(" + data.match.amount_balls + " balls remaining)";
                    }
                }
            }
            break;
        }

        case "current": {
            var innings = data.live.innings;

            out.score = [
                teams[innings.batting_team_id].team_abbreviation.toUpperCase() + " " + innings.runs,
                (innings.wickets < 10) ? "/"+innings.wickets : "",
                ((innings.event && innings.event == "declared") ? "d" : "")
            ].join('');

            out.overs = innings.overs;

            if (+data.match.scheduled_overs > 0) {
                switch (+innings.innings_number) {
                    case 1:
                        out.lead = "Run rate: "+innings.run_rate;
                        break;
                    default:
                        out.lead =
                            "Need "+(1-innings.lead)+" from "+
                            (innings.remaining_overs <= 10.0 ? innings.remaining_balls : innings.remaining_overs+" ov");
                        break;
                }
            }
            else {
                switch (+innings.innings_number) {
                    case 1:
                        out.lead = "First innings";
                        break;
                    case 4:
                        out.lead = "Target "+(innings.target);
                        break;
                    default:
                        out.lead =
                            innings.lead < 0 ? "Trail by "+(-innings.lead) :
                            innings.lead > 0 ? "Lead by "+innings.lead :
                                            "Scores level";
                }
            }

            var striker = data.live.batting.filter(function (player) { return player.live_current_name == "striker"; })[0];
            var nonstriker = data.live.batting.filter(function (player) { return player.live_current_name == "non-striker"; })[0];

            if (!striker) {
                striker = nonstriker;
                nonstriker = null;
            }

            if (striker) {
                out.striker_name = player_pretty_name(players[striker.player_id]);
                out.striker_stats = striker.runs+" ("+striker.balls_faced+")";
            }

            if (nonstriker) {
                out.nonstriker_name = player_pretty_name(players[nonstriker.player_id]);
                out.nonstriker_stats = nonstriker.runs+" ("+nonstriker.balls_faced+")";
            }

            var bowler = data.live.bowling.filter(function (player) { return player.live_current_name == "current bowler"; })[0];
            if (bowler) {
                out.bowler_name = player_pretty_name(players[bowler.player_id]);
                out.bowler_stats = bowler.overs+"-"+bowler.maidens+"-"+bowler.conceded+"-"+bowler.wickets;
            }

            var facts = [];

            if (data.live["break"]) {
                facts.push(data.live["break"]);
            }

            var factBall = localStorage.getItem("lastFactBall") || 0;

            console.log("lastFactBall: "+factBall);

            var newLastFactBall = factBall;
            data.comms.forEach(function (over) {
                over.ball.forEach(function (ball) {
                    if (ball.overs_unique > newLastFactBall) {
                        newLastFactBall = ball.overs_unique;
                    }

                    if (ball.event) {
                        var fact;

                        var ev = ball.event.match(/OUT|SIX|FOUR/);
                        if (!ev && ball.dismissal) {
                            ev = ["OUT"];
                        }

                        if (ev) switch (ev[0]) {

                            case "OUT":
                                var dismissal =
                                    ball.dismissal
                                    .replace(/\s+/g, " ")
                                    .replace("†", "\u2020")
                                    .replace("&", "&")
                                    .match(/(.+?) (lbw b|hit wicket b|run out|retired hurt|c \& b|c|b|st) ((?:.(?! (?:b|\d+)))+.)/);

                                fact = player_pretty_name(playerByShortName[dismissal[1]] || playerByLongName[dismissal[1]]) +
                                        " " + dismissal[2] +
                                        (dismissal[2].match(/run out|retired hurt/) ? "" : " " + dismissal[3]);
                                break;

                            case "SIX":
                            case "FOUR":
                                var name = ball.players.match(/to (.+)$/)[1];
                                fact = ev[0] + " " + player_pretty_name(playerByShortName[name] || playerByLongName[name]);
                                break;
                        }

                        if (fact) {
                            facts.push([ball.overs_actual,fact].join(' '));
                        }
                    }
                });
            });

            facts.forEach(function (fact) { console.log("FACT: "+fact); });

            console.log("newLastFactBall: "+newLastFactBall);
            if (newLastFactBall > factBall) {
                localStorage.setItem("lastFactBall", newLastFactBall);
            }

            // XXX send the lot to watch and have it cycle
            if (facts.length > 0) {
                out.fact = facts[0];
            }

            break;
        }
    }
}

function new_out () {
    var out = {
        "score":"",
        "overs":"",
        "lead":"",
        "striker_name":"",
        "striker_stats":"",
        "nonstriker_name":"",
        "nonstriker_stats":"",
        "bowler_name":"",
        "bowler_stats":"",
        "fact":""
    };
    return out;
}

function update () {
    var out = new_out();
    var matchId = localStorage.getItem("matchId");
    if (!matchId) {
        out.striker_name = "No match selected";
        out.nonstriker_name = "in config.";
        Pebble.sendAppMessage(out);
        return;
    }
    var req = new XMLHttpRequest();
    req.open('GET', "http://www.espncricinfo.com/ci/engine/match/"+matchId+".json");
    req.onload = function(e) {
        if (req.readyState == 4) {
            if(req.status == 200) {
                console.log("got new data, updating");

                var data = JSON.parse(req.responseText);
                if (data) {
                    unpack_data(data, out);
                    console.log(JSON.stringify(out));
                    Pebble.sendAppMessage(out);
                }

            } else {
                console.log("Error");
            }
        }
    };
    req.send(null);
}

Pebble.addEventListener("ready", function (e) {
    console.log("connected, doing first update");
    update();
});

Pebble.addEventListener("appmessage", function (e) {
    console.log("tick, doing update");
    update();
});

Pebble.addEventListener("showConfiguration", function (e) {
    console.log("config requested");
    Pebble.openURL("http://pebble.robn.io/sixfour/config.html");
});

Pebble.addEventListener("webviewclosed", function (e) {
    console.log("config closed, response: "+e.response);
    if (e.response !== "CANCELLED") {
        localStorage.setItem("matchId", e.response);
        localStorage.setItem("lastFactBall", 0);
        update();
    }
});
<commit_msg>Add break fact to the end of the fact list<commit_after>var measure = function (n) {
    var len = n.length;
    var nspaces = (n.match(/ /g) || []).length;
    var nwide   = (n.match(/[WwMm]/g) || []).length;
    var nnarrow = (n.match(/[Iil]/g) || []).length;
    var nnormal = n.length - nspaces - nwide - nnarrow;
    return nnormal + nspaces*0.8 + nnarrow*0.5 + nwide*1.5;
};

var shorten = function (n) {
    var bits = n.split(' ');
    for (var i = 0; i < bits.length-1; i++)
        bits[i] = bits[i].substr(0,1);
    return bits.join('.');
};

var player_pretty_name = function (p) {
    var name = p.card_short;
    if (measure(name) > 10 && p.popular_name.length > 0)
        name = p.popular_name;
    if (measure(name) > 10)
        name = shorten(name);
    return name;
};

function unpack_data (data, out) {
    var teams = {};
    var players = {};
    var playerByShortName = {};
    var playerByLongName = {};
    data.team.forEach(function (team) {
        teams[team.team_id] = team;
        var playerList = team.player || team.squad;
        playerList.forEach(function (player) {
            players[player.player_id] = player;
            playerByShortName[player.card_short] = player;
            playerByLongName[player.card_long] = player;
        });
    });

    var match_status = data.match.match_status;
    if ((!match_status || match_status === "current") && data.match.result !== "0")
        match_status = "complete";

    if (match_status === "current" && !data.live.innings.batting_team_id) {
        match_status = "dormant";
    }

    switch (match_status) {
        case "dormant":
            out.score = data.match.team1_abbreviation.toUpperCase()+" v "+data.match.team2_abbreviation.toUpperCase();

            if (data.live["break"]) {
                out.fact = data.live["break"];
            }

            if (data.match.match_clock && data.match.match_clock !== "") {
                out.lead = "Match starts in "+data.match.match_clock;
            }
            else if (out.fact !== "") {
                out.lead = out.fact;
                out.fact = "";
            }

            if (data.match.toss_decision && data.match.toss_decision !== "" && data.match.toss_decision !== "0") {
                out.striker_name = teams[data.match.toss_winner_team_id].team_short_name+" won toss,";
                out.nonstriker_name = "will "+data.match.toss_decision_name;
            }
            break;

        case "complete": {
            out.score = data.match.team1_abbreviation.toUpperCase()+" v "+data.match.team2_abbreviation.toUpperCase();

            if (data.match.winner_team_id == "0") {
                out.striker_name = "Match drawn";
            }
            else {
                out.striker_name = teams[data.match.winner_team_id].team_short_name+" won by";
                if (data.match.amount_name === "innings") {
                    out.nonstriker_name = "innings and "+data.match.amount+" runs";
                }
                else {
                    out.nonstriker_name = data.match.amount+" "+data.match.amount_name;
                    if (data.match.amount_balls && data.match.amount_balls > 0) {
                        out.bowler_name = "(" + data.match.amount_balls + " balls remaining)";
                    }
                }
            }
            break;
        }

        case "current": {
            var innings = data.live.innings;

            out.score = [
                teams[innings.batting_team_id].team_abbreviation.toUpperCase() + " " + innings.runs,
                (innings.wickets < 10) ? "/"+innings.wickets : "",
                ((innings.event && innings.event == "declared") ? "d" : "")
            ].join('');

            out.overs = innings.overs;

            if (+data.match.scheduled_overs > 0) {
                switch (+innings.innings_number) {
                    case 1:
                        out.lead = "Run rate: "+innings.run_rate;
                        break;
                    default:
                        out.lead =
                            "Need "+(1-innings.lead)+" from "+
                            (innings.remaining_overs <= 10.0 ? innings.remaining_balls : innings.remaining_overs+" ov");
                        break;
                }
            }
            else {
                switch (+innings.innings_number) {
                    case 1:
                        out.lead = "First innings";
                        break;
                    case 4:
                        out.lead = "Target "+(innings.target);
                        break;
                    default:
                        out.lead =
                            innings.lead < 0 ? "Trail by "+(-innings.lead) :
                            innings.lead > 0 ? "Lead by "+innings.lead :
                                            "Scores level";
                }
            }

            var striker = data.live.batting.filter(function (player) { return player.live_current_name == "striker"; })[0];
            var nonstriker = data.live.batting.filter(function (player) { return player.live_current_name == "non-striker"; })[0];

            if (!striker) {
                striker = nonstriker;
                nonstriker = null;
            }

            if (striker) {
                out.striker_name = player_pretty_name(players[striker.player_id]);
                out.striker_stats = striker.runs+" ("+striker.balls_faced+")";
            }

            if (nonstriker) {
                out.nonstriker_name = player_pretty_name(players[nonstriker.player_id]);
                out.nonstriker_stats = nonstriker.runs+" ("+nonstriker.balls_faced+")";
            }

            var bowler = data.live.bowling.filter(function (player) { return player.live_current_name == "current bowler"; })[0];
            if (bowler) {
                out.bowler_name = player_pretty_name(players[bowler.player_id]);
                out.bowler_stats = bowler.overs+"-"+bowler.maidens+"-"+bowler.conceded+"-"+bowler.wickets;
            }

            var facts = [];

            var factBall = localStorage.getItem("lastFactBall") || 0;

            console.log("lastFactBall: "+factBall);

            var newLastFactBall = factBall;
            data.comms.forEach(function (over) {
                over.ball.forEach(function (ball) {
                    if (ball.overs_unique > newLastFactBall) {
                        newLastFactBall = ball.overs_unique;
                    }

                    if (ball.event) {
                        var fact;

                        var ev = ball.event.match(/OUT|SIX|FOUR/);
                        if (!ev && ball.dismissal) {
                            ev = ["OUT"];
                        }

                        if (ev) switch (ev[0]) {

                            case "OUT":
                                var dismissal =
                                    ball.dismissal
                                    .replace(/\s+/g, " ")
                                    .replace("†", "\u2020")
                                    .replace("&", "&")
                                    .match(/(.+?) (lbw b|hit wicket b|run out|retired hurt|c \& b|c|b|st) ((?:.(?! (?:b|\d+)))+.)/);

                                fact = player_pretty_name(playerByShortName[dismissal[1]] || playerByLongName[dismissal[1]]) +
                                        " " + dismissal[2] +
                                        (dismissal[2].match(/run out|retired hurt/) ? "" : " " + dismissal[3]);
                                break;

                            case "SIX":
                            case "FOUR":
                                var name = ball.players.match(/to (.+)$/)[1];
                                fact = ev[0] + " " + player_pretty_name(playerByShortName[name] || playerByLongName[name]);
                                break;
                        }

                        if (fact) {
                            facts.push([ball.overs_actual,fact].join(' '));
                        }
                    }
                });
            });

            if (data.live["break"]) {
                facts.push(data.live["break"]);
            }

            facts.forEach(function (fact) { console.log("FACT: "+fact); });

            console.log("newLastFactBall: "+newLastFactBall);
            if (newLastFactBall > factBall) {
                localStorage.setItem("lastFactBall", newLastFactBall);
            }

            // XXX send the lot to watch and have it cycle
            if (facts.length > 0) {
                out.fact = facts[0];
            }

            break;
        }
    }
}

function new_out () {
    var out = {
        "score":"",
        "overs":"",
        "lead":"",
        "striker_name":"",
        "striker_stats":"",
        "nonstriker_name":"",
        "nonstriker_stats":"",
        "bowler_name":"",
        "bowler_stats":"",
        "fact":""
    };
    return out;
}

function update () {
    var out = new_out();
    var matchId = localStorage.getItem("matchId");
    if (!matchId) {
        out.striker_name = "No match selected";
        out.nonstriker_name = "in config.";
        Pebble.sendAppMessage(out);
        return;
    }
    var req = new XMLHttpRequest();
    req.open('GET', "http://www.espncricinfo.com/ci/engine/match/"+matchId+".json");
    req.onload = function(e) {
        if (req.readyState == 4) {
            if(req.status == 200) {
                console.log("got new data, updating");

                var data = JSON.parse(req.responseText);
                if (data) {
                    unpack_data(data, out);
                    console.log(JSON.stringify(out));
                    Pebble.sendAppMessage(out);
                }

            } else {
                console.log("Error");
            }
        }
    };
    req.send(null);
}

Pebble.addEventListener("ready", function (e) {
    console.log("connected, doing first update");
    update();
});

Pebble.addEventListener("appmessage", function (e) {
    console.log("tick, doing update");
    update();
});

Pebble.addEventListener("showConfiguration", function (e) {
    console.log("config requested");
    Pebble.openURL("http://pebble.robn.io/sixfour/config.html");
});

Pebble.addEventListener("webviewclosed", function (e) {
    console.log("config closed, response: "+e.response);
    if (e.response !== "CANCELLED") {
        localStorage.setItem("matchId", e.response);
        localStorage.setItem("lastFactBall", 0);
        update();
    }
});
<|endoftext|>


<commit_before>import _ from 'lodash';

//  Returns a random configuration of a module's timetable lessons.
//  Used when a module is first added.
export function randomLessonConfiguration(lessons) {
  return _(lessons)
    .groupBy('LessonType')
    .mapValues((group) => {
      return _.groupBy(group, 'ClassNo');
    })
    .mapValues((group) => {
      return _.sample(group);
    })
    .value();
}

//  Converts from timetable format to flat array of lessons.
//  {
//    [ModuleCode]: {
//      [LessonType]: [
//        { ...Lesson },
//        { ...Lesson },
//      ],
//      ...
//    }
//  }
export function timetableLessonsArray(timetable) {
  return _.flatMapDepth(timetable, (lessonType) => {
    return _.values(lessonType);
  }, 2);
}

//  Groups flat array of lessons by day.
//  {
//    Monday: [{ ...Lesson }, { ...Lesson }, ...],
//    Tuesday: [{ ...Lesson }, { ...Lesson }, ...]
//  }
export function groupLessonsByDay(lessons) {
  return _.groupBy(lessons, 'DayText');
}

//  Determines if two lessons of the same day overlap. Only start/end time is being checked
export function doLessonsOverlap(lesson1, lesson2) {
  return lesson1.StartTime <= lesson2.EndTime && lesson2.StartTime <= lesson1.EndTime;
}

//  Converts a flat array of lessons *within a day* into rows:
//  Result invariants:
//  - Each lesson will not overlap each other.
//  [
//    [{ ...Lesson }, { ...Lesson }, ...],
//    [{ ...Lesson }, ...],
//  ]
export function arrangeLessonsWithinDay(lessons) {
  const rows = [[]];
  if (_.isEmpty(lessons)) {
    return rows;
  }

  lessons.forEach((lesson) => {
    for (let i = 0, length = rows.length; i < length; i++) {
      const rowLessons = rows[i];
      // Search through lessons in row to look for available slots.
      const overlapTests = _.map(rowLessons, (rowLesson) => {
        return !doLessonsOverlap(rowLesson, lesson);
      });
      if (_.every(overlapTests)) {
        // Lesson does not overlap with any lessons in the row. Add it to row.
        rowLessons.push(lesson);
        return;
      }
    }
    rows.push([lesson]);
  });

  return rows;
}

export function arrangeLessonsForWeek(lessons) {
  const dayLessons = groupLessonsByDay(lessons);
  return _.mapValues(dayLessons, (dayLesson) => {
    return arrangeLessonsWithinDay(dayLesson);
  });
}
<commit_msg>Fix overlapping logic bug<commit_after>import _ from 'lodash';

//  Returns a random configuration of a module's timetable lessons.
//  Used when a module is first added.
export function randomLessonConfiguration(lessons) {
  return _(lessons)
    .groupBy('LessonType')
    .mapValues((group) => {
      return _.groupBy(group, 'ClassNo');
    })
    .mapValues((group) => {
      return _.sample(group);
    })
    .value();
}

//  Converts from timetable format to flat array of lessons.
//  {
//    [ModuleCode]: {
//      [LessonType]: [
//        { ...Lesson },
//        { ...Lesson },
//      ],
//      ...
//    }
//  }
export function timetableLessonsArray(timetable) {
  return _.flatMapDepth(timetable, (lessonType) => {
    return _.values(lessonType);
  }, 2);
}

//  Groups flat array of lessons by day.
//  {
//    Monday: [{ ...Lesson }, { ...Lesson }, ...],
//    Tuesday: [{ ...Lesson }, { ...Lesson }, ...]
//  }
export function groupLessonsByDay(lessons) {
  return _.groupBy(lessons, 'DayText');
}

//  Determines if two lessons of the same day overlap. Only start/end time is being checked
export function doLessonsOverlap(lesson1, lesson2) {
  return lesson1.StartTime < lesson2.EndTime && lesson2.StartTime < lesson1.EndTime;
}

//  Converts a flat array of lessons *within a day* into rows:
//  Result invariants:
//  - Each lesson will not overlap each other.
//  [
//    [{ ...Lesson }, { ...Lesson }, ...],
//    [{ ...Lesson }, ...],
//  ]
export function arrangeLessonsWithinDay(lessons) {
  const rows = [[]];
  if (_.isEmpty(lessons)) {
    return rows;
  }

  lessons.forEach((lesson) => {
    for (let i = 0, length = rows.length; i < length; i++) {
      const rowLessons = rows[i];
      // Search through lessons in row to look for available slots.
      const overlapTests = _.map(rowLessons, (rowLesson) => {
        return !doLessonsOverlap(rowLesson, lesson);
      });
      if (_.every(overlapTests)) {
        // Lesson does not overlap with any lessons in the row. Add it to row.
        rowLessons.push(lesson);
        return;
      }
    }
    rows.push([lesson]);
  });

  return rows;
}

export function arrangeLessonsForWeek(lessons) {
  const dayLessons = groupLessonsByDay(lessons);
  return _.mapValues(dayLessons, (dayLesson) => {
    return arrangeLessonsWithinDay(dayLesson);
  });
}
<|endoftext|>


<commit_before>"use strict";

/**
 * Yields the items of the iterator sorted by a given comparator.
 *
 * @this {Iterable<T>}
 * @ntime Engine-specific
 * @dspace Engine-specific
 * @example Basic Usage
 *
 * ```javascript
 * ["foo", "bar"]::sortAlphabetically() // yields "bar", "foo"
 * "cba"::sortAlphabetically() // yields "a", "b", "c"
 * ```
*/
export function * sortAlphabetically <T> (

) : Iterable<T> {
};
<commit_msg>Implemented iterable/sortAlphabetically.<commit_after>"use strict";

/**
 * Yields the items of the iterator sorted by a given comparator.
 *
 * @this {Iterable<T>}
 * @ntime Engine-specific
 * @dspace Engine-specific
 * @example Basic Usage
 *
 * ```javascript
 * ["foo", "bar"]::sortAlphabetically() // yields "bar", "foo"
 * "cba"::sortAlphabetically() // yields "a", "b", "c"
 * ```
*/
export function * sortAlphabetically <T> (

) : Iterable<T> {
    yield * [...this].sort();
};
<|endoftext|>


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

    var Utility = require('common/utility');

    describe('Utility', function() {
        it('Should be able to convert ISO8061 Duration to Seconds', function() {
            expect(Utility.iso8061DurationToSeconds('PT0H0M0S')).to.equal(0);
            expect(Utility.iso8061DurationToSeconds('PT0H0M1S')).to.equal(1);
            expect(Utility.iso8061DurationToSeconds('PT0H0M60S')).to.equal(60);
            expect(Utility.iso8061DurationToSeconds('PT0H1M0S')).to.equal(60);
            expect(Utility.iso8061DurationToSeconds('PT0H60M0S')).to.equal(3600);
            expect(Utility.iso8061DurationToSeconds('PT1H0M0S')).to.equal(3600);
            // 52S + (3 * 60S) + (1 * 60 * 60S) = 3832
            expect(Utility.iso8061DurationToSeconds('PT1H3M52S')).to.equal(3832);
        });

        it('Should be able to pretty print time in seconds.', function() {
            expect(Utility.prettyPrintTime(0)).to.equal('00:00');
            expect(Utility.prettyPrintTime(1)).to.equal('00:01');
            expect(Utility.prettyPrintTime(60)).to.equal('01:00');
            expect(Utility.prettyPrintTime(61)).to.equal('01:01');
            expect(Utility.prettyPrintTime(600)).to.equal('10:00');
            expect(Utility.prettyPrintTime(601)).to.equal('10:01');
            expect(Utility.prettyPrintTime(601)).to.equal('10:01');
            expect(Utility.prettyPrintTime(3599)).to.equal('59:59');
            expect(Utility.prettyPrintTime(3600)).to.equal('1:00:00');
            expect(Utility.prettyPrintTime(3601)).to.equal('1:00:01');
            expect(Utility.prettyPrintTime(86400)).to.equal('24:00:00');
        });

        it('Should be able to cleanse a YouTube song title', function() {
            expect(Utility.cleanTitle(' ')).to.equal('');
            expect(Utility.cleanTitle('**NEW**')).to.equal('');
            expect(Utility.cleanTitle('[1999]')).to.equal('');
            expect(Utility.cleanTitle('(1999)')).to.equal('');
            expect(Utility.cleanTitle('(best version)')).to.equal('');
            expect(Utility.cleanTitle('official music video')).to.equal('');
            expect(Utility.cleanTitle('ALBUM TRACK')).to.equal('');
            expect(Utility.cleanTitle('(official)')).to.equal('');
            expect(Utility.cleanTitle('x (HD)')).to.equal('x');
            expect(Utility.cleanTitle('x HQ ')).to.equal('x');
            expect(Utility.cleanTitle('video clip')).to.equal('');
            expect(Utility.cleanTitle('x live')).to.equal('x');
            expect(Utility.cleanTitle('with lyrics')).to.equal('');
            expect(Utility.cleanTitle('artist - "track title"')).to.equal('track title');
        });

        it('should be able to shift an offset when it exceeds the right-side of the container', function() {
            var offset = 950;
            var containerLength = 1000;
            var elementLength = 100;

            debugger;
            var adjustedOffset = Utility.shiftOffset(offset, elementLength, containerLength);
            expect(adjustedOffset).not.to.equal(offset);
            expect(adjustedOffset).to.equal(900);
        });

        it('should be able to shift an offset when it exceeds the left-side of the container', function() {
            var offset = -50;
            var containerLength = 1000;
            var elementLength = 100;

            var adjustedOffset = Utility.shiftOffset(offset, elementLength, containerLength);
            expect(adjustedOffset).not.to.equal(offset);
            expect(adjustedOffset).to.equal(0);
        });

        it('should be able to not shift an offset when its left-edge fits in the container', function() {
            var offset = 0;
            var containerLength = 1000;
            var elementLength = 100;

            var adjustedOffset = Utility.shiftOffset(offset, elementLength, containerLength);
            expect(adjustedOffset).to.equal(offset);
        });

        it('should be able to not shift an offset when its right-edge fits in the container', function() {
            var offset = 900;
            var containerLength = 1000;
            var elementLength = 100;

            var adjustedOffset = Utility.shiftOffset(offset, elementLength, containerLength);
            expect(adjustedOffset).to.equal(offset);
        });

        it('should be able to flipInvert an offset when trying to position the element below the target', function() {
            var offset = 950;
            var containerLength = 1000;
            var adjust = 0;
            var targetLength = 200;
            var elementLength = 100;

            var adjustedOffset = Utility.flipInvertOffset(offset, elementLength, containerLength, targetLength, adjust);
            expect(adjustedOffset).not.to.equal(offset);
            expect(adjustedOffset).to.equal(650);
        });

        it('should be able to flipInvert an offset when trying to position the element above the target', function() {
            var offset = -50;
            var containerLength = 1000;
            var adjust = 0;
            var targetLength = 200;
            var elementLength = 100;

            var adjustedOffset = Utility.flipInvertOffset(offset, elementLength, containerLength, targetLength, adjust);
            expect(adjustedOffset).not.to.equal(offset);
            expect(adjustedOffset).to.equal(250);
        });

        it('should be able to not flipInvert an offset', function() {
            var offset = 0;
            var containerLength = 1000;
            var adjust = 0;
            var targetLength = 200;
            var elementLength = 100;

            var adjustedOffset = Utility.flipInvertOffset(offset, elementLength, containerLength, targetLength, adjust);
            expect(adjustedOffset).to.equal(offset);
        });
    });
});<commit_msg>removing debugger<commit_after>define(function(require) {
    'use strict';

    var Utility = require('common/utility');

    describe('Utility', function() {
        it('Should be able to convert ISO8061 Duration to Seconds', function() {
            expect(Utility.iso8061DurationToSeconds('PT0H0M0S')).to.equal(0);
            expect(Utility.iso8061DurationToSeconds('PT0H0M1S')).to.equal(1);
            expect(Utility.iso8061DurationToSeconds('PT0H0M60S')).to.equal(60);
            expect(Utility.iso8061DurationToSeconds('PT0H1M0S')).to.equal(60);
            expect(Utility.iso8061DurationToSeconds('PT0H60M0S')).to.equal(3600);
            expect(Utility.iso8061DurationToSeconds('PT1H0M0S')).to.equal(3600);
            // 52S + (3 * 60S) + (1 * 60 * 60S) = 3832
            expect(Utility.iso8061DurationToSeconds('PT1H3M52S')).to.equal(3832);
        });

        it('Should be able to pretty print time in seconds.', function() {
            expect(Utility.prettyPrintTime(0)).to.equal('00:00');
            expect(Utility.prettyPrintTime(1)).to.equal('00:01');
            expect(Utility.prettyPrintTime(60)).to.equal('01:00');
            expect(Utility.prettyPrintTime(61)).to.equal('01:01');
            expect(Utility.prettyPrintTime(600)).to.equal('10:00');
            expect(Utility.prettyPrintTime(601)).to.equal('10:01');
            expect(Utility.prettyPrintTime(601)).to.equal('10:01');
            expect(Utility.prettyPrintTime(3599)).to.equal('59:59');
            expect(Utility.prettyPrintTime(3600)).to.equal('1:00:00');
            expect(Utility.prettyPrintTime(3601)).to.equal('1:00:01');
            expect(Utility.prettyPrintTime(86400)).to.equal('24:00:00');
        });

        it('Should be able to cleanse a YouTube song title', function() {
            expect(Utility.cleanTitle(' ')).to.equal('');
            expect(Utility.cleanTitle('**NEW**')).to.equal('');
            expect(Utility.cleanTitle('[1999]')).to.equal('');
            expect(Utility.cleanTitle('(1999)')).to.equal('');
            expect(Utility.cleanTitle('(best version)')).to.equal('');
            expect(Utility.cleanTitle('official music video')).to.equal('');
            expect(Utility.cleanTitle('ALBUM TRACK')).to.equal('');
            expect(Utility.cleanTitle('(official)')).to.equal('');
            expect(Utility.cleanTitle('x (HD)')).to.equal('x');
            expect(Utility.cleanTitle('x HQ ')).to.equal('x');
            expect(Utility.cleanTitle('video clip')).to.equal('');
            expect(Utility.cleanTitle('x live')).to.equal('x');
            expect(Utility.cleanTitle('with lyrics')).to.equal('');
            expect(Utility.cleanTitle('artist - "track title"')).to.equal('track title');
        });

        it('should be able to shift an offset when it exceeds the right-side of the container', function() {
            var offset = 950;
            var containerLength = 1000;
            var elementLength = 100;

            var adjustedOffset = Utility.shiftOffset(offset, elementLength, containerLength);
            expect(adjustedOffset).not.to.equal(offset);
            expect(adjustedOffset).to.equal(900);
        });

        it('should be able to shift an offset when it exceeds the left-side of the container', function() {
            var offset = -50;
            var containerLength = 1000;
            var elementLength = 100;

            var adjustedOffset = Utility.shiftOffset(offset, elementLength, containerLength);
            expect(adjustedOffset).not.to.equal(offset);
            expect(adjustedOffset).to.equal(0);
        });

        it('should be able to not shift an offset when its left-edge fits in the container', function() {
            var offset = 0;
            var containerLength = 1000;
            var elementLength = 100;

            var adjustedOffset = Utility.shiftOffset(offset, elementLength, containerLength);
            expect(adjustedOffset).to.equal(offset);
        });

        it('should be able to not shift an offset when its right-edge fits in the container', function() {
            var offset = 900;
            var containerLength = 1000;
            var elementLength = 100;

            var adjustedOffset = Utility.shiftOffset(offset, elementLength, containerLength);
            expect(adjustedOffset).to.equal(offset);
        });

        it('should be able to flipInvert an offset when trying to position the element below the target', function() {
            var offset = 950;
            var containerLength = 1000;
            var adjust = 0;
            var targetLength = 200;
            var elementLength = 100;

            var adjustedOffset = Utility.flipInvertOffset(offset, elementLength, containerLength, targetLength, adjust);
            expect(adjustedOffset).not.to.equal(offset);
            expect(adjustedOffset).to.equal(650);
        });

        it('should be able to flipInvert an offset when trying to position the element above the target', function() {
            var offset = -50;
            var containerLength = 1000;
            var adjust = 0;
            var targetLength = 200;
            var elementLength = 100;

            var adjustedOffset = Utility.flipInvertOffset(offset, elementLength, containerLength, targetLength, adjust);
            expect(adjustedOffset).not.to.equal(offset);
            expect(adjustedOffset).to.equal(250);
        });

        it('should be able to not flipInvert an offset', function() {
            var offset = 0;
            var containerLength = 1000;
            var adjust = 0;
            var targetLength = 200;
            var elementLength = 100;

            var adjustedOffset = Utility.flipInvertOffset(offset, elementLength, containerLength, targetLength, adjust);
            expect(adjustedOffset).to.equal(offset);
        });
    });
});<|endoftext|>


<commit_before>PhoneApp.use('PhoneApp.types.Object');
PhoneApp.pack('PhoneApp.types', function(api) {
  /*jshint camelcase:false*/
  'use strict';
  Array.prototype.replace = function(index, removeCount, adds) {
    var m;
    if (adds === undefined)
      adds = [];
    if (!adds || adds.constructor != Array)
      adds = [adds];
    m = adds.map(function(item) {return item;});
    m.unshift(removeCount);
    m.unshift(index);
    var remed = Array.prototype.splice.apply(this, m);
    if ('__hashcache__' in this)
      this.__hashcache__.forEach(function(item) {
        window.setTimeout(function() {
          item.apply(this, [index, adds, remed]);
        }.bind(this), 1);
      }, this);
    return remed;
  };

  Array.prototype.popObject = function() {
    return this.replace(this.length - 1, 1);
  };

  Array.prototype.pushObject = function(o) {
    return this.replace(this.length, 0, o);
  };

  Array.prototype.shiftObject = function() {
    return this.replace(0, 1);
  };

  Array.prototype.unshiftObject = function(o) {
    return this.replace(0, 0, o);
  };

  Array.prototype.removeObject = function(o) {
    var needle = this.indexOf(o);
    if (needle != -1)
      return this.replace(needle, 1);
  };

  Array.prototype.insertAt = function(index, o) {
    return this.replace(index, 0, o);
  };

  Array.prototype.removeAt = function(index) {
    return this.replace(index, 1);
  };

  Array.prototype.clear = function() {
    return this.replace(0, this.length);
  };

  Array.prototype.removeArrayObserver = function(listener) {
    if (!('__hashcache__' in this))
      return;
    var needle = this.__hashcache__.indexOf(listener);
    if (needle != -1)
      this.__hashcache__.splice(needle, 1);
  };

  Array.prototype.addArrayObserver = function(listener) {
    if (!('__hashcache__' in this))
      this.__hashcache__ = [];
    this.__hashcache__.push(listener);
  };

  var swap = function(arr, i, j) {
    if (i == j)
      return;
    var tmp = arr[i];
    tmp = arr.replace(j, 1, tmp);
    arr.replace(i, 1, tmp);
  };

  var quickSort = function(arr, compare, left, right) {
    if (left >= right)
      return;
    swap(arr, left, Math.floor((left + right) / 2));
    var i;
    var last = left;
    for (i = left + 1; i <= right; i++)
      if (compare(arr[i], arr[left]) < 0)
        swap(arr, ++last, i);
      swap(arr, left, last);
    quickSort(arr, compare, left, last - 1);
    quickSort(arr, compare, last + 1, right);
  };

  Array.prototype.quickSort = function(compare){
    quickSort(this, compare, 0, this.length - 1);
    return this;
  };

  this.ArrayController = api.Object.extend({
    init: function(){
      api.Object._super('init', this);

      var arr = false;
      var c = false;
      var m = false;
      var s = [];

      var filter = function() {
        return true;
      };

      var map = function(item) {
        return item;
      };

      var sort = function(a, b) {
        return (a < b) ? -1 : (a == b ? 0 : 1);
      };

      var obs = function(index, added, removed) {
        removed.forEach(function(item) {
          var needle = c.indexOf(item, index);
          if (needle != -1) {
            c.splice(needle, 1);
            item = m.splice(needle, 1);
            needle = s.indexOf(item);
            s.replace(needle, 1);
          }
        });

        added.forEach(function(item) {
          if (filter(item)) {
            c.splice(index, 0, item);
            item = map(item);
            m.splice(index, 0, item);
            var needle;
            s.some(function(sub, idx) {
              needle = idx;
              return sort(sub, item) >= 0;
            });
            s.replace(needle, 0, item);
          }
        });
      };

      this.destroy = function() {
        arr.removeArrayObserver(obs);
        arr = false;
        c = false;
        m = false;
        s.clear();
      };

      Object.defineProperty(this, 'original', {
        get: function(){
          return arr;
        }
      });

      Object.defineProperty(this, 'content', {
        enumerable: true,
        get: function() {
          return s;
        },
        set: function(a) {
          if (arr)
            arr.removeArrayObserver(obs);
          arr = a;
          arr.addArrayObserver(obs);
          c = arr.filter(filter);
          m = c.map(map);
          s.clear();
          m.forEach(function(item){
            s.pushObject(item);
          });
          s.quickSort(sort);
        }
      });

      Object.defineProperty(this, 'filter', {
        get: function() {
          return filter;
        },
        set: function(callback) {
          filter = callback;
          if (arr) {
            c = arr.filter(filter);
            m = c.map(map);
            s.clear();
            m.forEach(function(item){
              s.pushObject(item);
            });
            s.quickSort(sort);
          }
        }
      });

      Object.defineProperty(this, 'map', {
        get: function() {
          return map;
        },
        set: function(callback) {
          map = callback;
          if (arr) {
            m = c.map(map);
            s.clear();
            m.forEach(function(item){
              s.pushObject(item);
            });
            s.quickSort(sort);
          }
        }
      });

      Object.defineProperty(this, 'sort', {
        get: function() {
          return sort;
        },
        set: function(callback) {
          sort = callback;
          if (arr)
            s.quickSort(sort);
        }
      });
    }
  });

});

<commit_msg>Implement observable length on array controller<commit_after>PhoneApp.use('PhoneApp.types.Object');
PhoneApp.pack('PhoneApp.types', function(api) {
  /*jshint camelcase:false*/
  'use strict';
  Array.prototype.replace = function(index, removeCount, adds) {
    var m;
    if (adds === undefined)
      adds = [];
    if (!adds || adds.constructor != Array)
      adds = [adds];
    m = adds.map(function(item) {return item;});
    m.unshift(removeCount);
    m.unshift(index);
    var remed = Array.prototype.splice.apply(this, m);
    if ('__hashcache__' in this)
      this.__hashcache__.forEach(function(item) {
        window.setTimeout(function() {
          item.apply(this, [index, adds, remed]);
        }.bind(this), 1);
      }, this);
    return remed;
  };

  Array.prototype.popObject = function() {
    return this.replace(this.length - 1, 1);
  };

  Array.prototype.pushObject = function(o) {
    return this.replace(this.length, 0, o);
  };

  Array.prototype.shiftObject = function() {
    return this.replace(0, 1);
  };

  Array.prototype.unshiftObject = function(o) {
    return this.replace(0, 0, o);
  };

  Array.prototype.removeObject = function(o) {
    var needle = this.indexOf(o);
    if (needle != -1)
      return this.replace(needle, 1);
  };

  Array.prototype.insertAt = function(index, o) {
    return this.replace(index, 0, o);
  };

  Array.prototype.removeAt = function(index) {
    return this.replace(index, 1);
  };

  Array.prototype.clear = function() {
    return this.replace(0, this.length);
  };

  Array.prototype.removeArrayObserver = function(listener) {
    if (!('__hashcache__' in this))
      return;
    var needle = this.__hashcache__.indexOf(listener);
    if (needle != -1)
      this.__hashcache__.splice(needle, 1);
  };

  Array.prototype.addArrayObserver = function(listener) {
    if (!('__hashcache__' in this))
      this.__hashcache__ = [];
    this.__hashcache__.push(listener);
  };

  var swap = function(arr, i, j) {
    if (i == j)
      return;
    var tmp = arr[i];
    tmp = arr.replace(j, 1, tmp);
    arr.replace(i, 1, tmp);
  };

  var quickSort = function(arr, compare, left, right) {
    if (left >= right)
      return;
    swap(arr, left, Math.floor((left + right) / 2));
    var i;
    var last = left;
    for (i = left + 1; i <= right; i++)
      if (compare(arr[i], arr[left]) < 0)
        swap(arr, ++last, i);
      swap(arr, left, last);
    quickSort(arr, compare, left, last - 1);
    quickSort(arr, compare, last + 1, right);
  };

  Array.prototype.quickSort = function(compare){
    quickSort(this, compare, 0, this.length - 1);
    return this;
  };

  this.ArrayController = api.Object.extend({
    length: 0,
    init: function(){
      api.Object._super('init', this);

      var arr = false;
      var c = false;
      var m = false;
      var s = [];

      var filter = function() {
        return true;
      };

      var map = function(item) {
        return item;
      };

      var sort = function(a, b) {
        return (a < b) ? -1 : (a == b ? 0 : 1);
      };

      var obs = function(index, added, removed) {
        removed.forEach(function(item) {
          var needle = c.indexOf(item, index);
          if (needle != -1) {
            c.splice(needle, 1);
            item = m.splice(needle, 1);
            needle = s.indexOf(item);
            s.replace(needle, 1);
          }
        });

        added.forEach(function(item) {
          if (filter(item)) {
            c.splice(index, 0, item);
            item = map(item);
            m.splice(index, 0, item);
            var needle;
            s.some(function(sub, idx) {
              needle = idx;
              return sort(sub, item) >= 0;
            });
            s.replace(needle, 0, item);
          }
        });
        this.set('length', s.length);
      };

      this.destroy = function() {
        arr.removeArrayObserver(obs);
        arr = false;
        c = false;
        m = false;
        this.set('length', 0);
        s.clear();
      };

      Object.defineProperty(this, 'original', {
        get: function(){
          return arr;
        }
      });

      Object.defineProperty(this, 'content', {
        enumerable: true,
        get: function() {
          return s;
        },
        set: function(a) {
          if (arr)
            arr.removeArrayObserver(obs);
          arr = a;
          arr.addArrayObserver(obs);
          c = arr.filter(filter);
          m = c.map(map);
          s.clear();
          m.forEach(function(item){
            s.pushObject(item);
          });
          s.quickSort(sort);
          this.set('length', s.length);
        }
      });

      Object.defineProperty(this, 'filter', {
        get: function() {
          return filter;
        },
        set: function(callback) {
          filter = callback;
          if (arr) {
            c = arr.filter(filter);
            m = c.map(map);
            s.clear();
            m.forEach(function(item){
              s.pushObject(item);
            });
            s.quickSort(sort);
            this.set('length', s.length);
          }
        }
      });

      Object.defineProperty(this, 'map', {
        get: function() {
          return map;
        },
        set: function(callback) {
          map = callback;
          if (arr) {
            m = c.map(map);
            s.clear();
            m.forEach(function(item){
              s.pushObject(item);
            });
            s.quickSort(sort);
            this.set('length', s.length);
          }
        }
      });

      Object.defineProperty(this, 'sort', {
        get: function() {
          return sort;
        },
        set: function(callback) {
          sort = callback;
          if (arr){
            s.quickSort(sort);
            this.set('length', s.length);
          }
        }
      });
    }
  });

});

<|endoftext|>


<commit_before>import "./vendor"
import sound from "./sound"
import Fire from "./fire"
import DB from "./db"
import $ from "jquery"
import _ from "ramda"
import Notifications from "./notifications"
import cryptoRandomString from "crypto-random-string"
// import autosize from "autosize"
import localforage from "localforage"
import MutationSummary from "mutation-summary"
import {Kefir} from "kefir"

// noinspection NpmUsedModulesInstalled
import {Main} from "elm/Main.elm"

//noinspection JSUnresolvedVariable
const isDevelopmentMode = process.env["NODE_ENV"] === "development"
const env = process.env
const npmPackageVersion = env["npm_package_version"]

//test

/*global.xhot = module.hot

 if(module.hot){
 module.hot.addStatusHandler(status => {
 console.warn("hot status", status);
 })
 }*/

const mutationObserverFocusSelectorStream = Kefir.stream(emitter => {

  const focusableEntityListItemSelector = ".focusable-list-item[tabindex=0]"
  const autoFocusSelector = ".auto-focus"
  const observer = new MutationSummary({
    callback: summaries => {
      // console.log(summaries)

      const autoFocusSummaryAdded = summaries[0].added
      if (!_.isEmpty(autoFocusSummaryAdded)) {
        emitter.emit(".auto-focus")
      }

      const focusInEntitySummary = summaries[1]
      const focusInEntitySummaryAdded = focusInEntitySummary.added
      if (!_.isEmpty(focusInEntitySummaryAdded)) {
        // console.log("focusInEntitySummary.added", focusInEntitySummaryAdded)
        emitter.emit(".focusable-list-item[tabindex=0]")
      }
    },
    queries: [{element: autoFocusSelector},
      {element: focusableEntityListItemSelector},
    ],
  })
})

window.appBoot = async function appBoot(elmMain = Main) {

  const deviceId = getOrCreateDeviceId()
  const $elm = $("#elm-container")
  // $elm.trap();

  $elm.on("keydown", `.entity-list`, e => {
    // console.log(e.keyCode, e.key, e.target, e);

    // prevent document scrolling
    if (e.key === " " || e.key === "ArrowUp" || e.key === "ArrowDown") {
      e.preventDefault()
    }

  })

  const db = await DB()

  const store = localforage.createInstance({
    name: "SimpleGTD.com offline store",
  });

  const localPref = await store.getItem("local-pref")

  const debugSecondMultiplier = (() => {
    if (WEBPACK_DEV_SERVER) {
      return 300000
    } else {
      return 1
    }
  })()

  const flags = _.merge({
    now: Date.now(),
    // encodedTodoList: await allDocsMap["todo-db"],
    // encodedProjectList: await allDocsMap["project-db"],
    // encodedContextList: await allDocsMap["context-db"],
    // encodedTodoList: [],
    // encodedProjectList: [],
    // encodedContextList: [],
    pouchDBRemoteSyncURI: localStorage.getItem("pouchdb.remote-sync-uri") || "",
    developmentMode: isDevelopmentMode,
    appVersion: npmPackageVersion,
    deviceId,
    config: {debugSecondMultiplier, deviceId, npmPackageVersion, isDevelopmentMode},
    localPref: localPref,
  }, db.allDocsMap)

  const app = elmMain
      .embed(document.getElementById("elm-container"), flags)

  const fire = Fire.setup(app, _.values(db.list), deviceId)

  db.setupApp(app)

  app.ports["persistLocalPref"].subscribe(async (localPref) => {
    store.setItem("local-pref", localPref)
         .catch(console.error)
  });

  Notifications.setup(fire, app).catch(console.error)

  const portFocusSelectorStream =
      Kefir.stream(emitter => {
        app.ports["focusSelector"].subscribe((selector) => {
          // console.log("port: focusSelector received selector", selector)
          emitter.emit(selector)
        })
      })

  const addMetadataToFocusSelectorStream =
      (stream, streamName) => stream.map(selector => ({streamName, selector}))

  const focusSelectorStreams =
      [addMetadataToFocusSelectorStream(
          portFocusSelectorStream,
          "portFocusSelectorStream",
      ),
       addMetadataToFocusSelectorStream(
           mutationObserverFocusSelectorStream,
           "mutationObserverFocusSelectorStream",
       )]
  Kefir.merge(focusSelectorStreams)
       .debounce(100)
       .observe({
         value(options) {
           const $toFocus = $(options.selector).first()

           console.log("[Kefir] focusSelector", _.merge(options, {
             dataKey: $toFocus.data("key"),
           }))

           $toFocus.focus()
         },
       })

  app.ports["focusSelectorIfNoFocus"].subscribe((selector) => {
    const $focus = $(":focus, [focused]")
    // console.log($focus, $focus.length)
    if ($focus.length === 0) {
      $(selector).focus()
    }
  })

  app.ports["positionPopupMenu"].subscribe((ofSelector) => {
    requestAnimationFrame(() => {
      const $popup = $("#popup-menu")
      $popup.position({
        my: "right top",
        at: "right top",
        of: $(ofSelector),
        within: ".overlay",
        collision: "fit",
      })
      $popup.find(".auto-focus").first().focus()

      const focusableSelector = ":focusable, input:focusable"
      $popup.on("keydown", focusableSelector, function (e) {
        if (e.key !== "Tab") return
        const $focusable = $popup.find(focusableSelector)

        const first = $focusable.first().get(0)
        const last = $focusable.last().get(0)
        const dateTimeInputSelector = `input[type="date"], input[type="time"]`

        if (e.shiftKey) {

          if (this === first) {
            e.preventDefault()
            e.stopPropagation()
            last.focus()
          } else if ($(this).is(dateTimeInputSelector)) {
            e.preventDefault()
            e.stopPropagation()
            $focusable.get($.inArray(this, $focusable) - 1).focus()
          }
        }
        else {

          if (this === last) {
            e.preventDefault()
            e.stopPropagation()
            first.focus()
          } else if ($(this).is(dateTimeInputSelector)) {
            e.preventDefault()
            e.stopPropagation()
            $focusable.get($.inArray(this, $focusable) + 1).focus()
          }
        }
      })
    })
  })

  app.ports["startAlarm"].subscribe(() => {
    sound.start()
  })

  app.ports["stopAlarm"].subscribe(() => {
    sound.stop()
  })
}

function getOrCreateDeviceId() {
  let deviceId = localStorage.getItem("device-id")
  if (!deviceId) {
    deviceId = cryptoRandomString(64)
    localStorage.setItem("device-id", deviceId)
  }
  return deviceId
}

function getOrCreateFirstVisit() {
  let firstVisit = localStorage.getItem("first-visit")
  if (!firstVisit) {
    localStorage.setItem("first-visit", "false")
  }
  return !!firstVisit
}
<commit_msg>fix(entity-list): add entity list to model to track focus changes in view.<commit_after>import "./vendor"
import sound from "./sound"
import Fire from "./fire"
import DB from "./db"
import $ from "jquery"
import _ from "ramda"
import Notifications from "./notifications"
import cryptoRandomString from "crypto-random-string"
// import autosize from "autosize"
import localforage from "localforage"
import MutationSummary from "mutation-summary"
import {Kefir} from "kefir"

// noinspection NpmUsedModulesInstalled
import {Main} from "elm/Main.elm"

//noinspection JSUnresolvedVariable
const isDevelopmentMode = process.env["NODE_ENV"] === "development"
const env = process.env
const npmPackageVersion = env["npm_package_version"]

//test

/*global.xhot = module.hot

 if(module.hot){
 module.hot.addStatusHandler(status => {
 console.warn("hot status", status);
 })
 }*/

const mutationObserverFocusSelectorStream = Kefir.stream(emitter => {

  const focusableEntityListItemSelector = ".focusable-list-item[tabindex=0]"
  const autoFocusSelector = ".auto-focus"
  const observer = new MutationSummary({
    callback: summaries => {
      // console.log(summaries)

      const autoFocusSummaryAdded = summaries[0].added
      if (!_.isEmpty(autoFocusSummaryAdded)) {
        emitter.emit(".auto-focus")
      }

      const focusInEntitySummary = summaries[1]
      const focusInEntitySummaryAdded = focusInEntitySummary.added
      if (!_.isEmpty(focusInEntitySummaryAdded)) {
        // console.log("focusInEntitySummary.added", focusInEntitySummaryAdded)
        emitter.emit(".focusable-list-item[tabindex=0]")
      }
    },
    queries: [{element: autoFocusSelector},
      {element: focusableEntityListItemSelector},
    ],
  })
})

window.appBoot = async function appBoot(elmMain = Main) {

  const deviceId = getOrCreateDeviceId()
  const $elm = $("#elm-container")
  // $elm.trap();

  $elm.on("keydown", `.entity-list`, e => {
    // console.log(e.keyCode, e.key, e.target, e);

    // prevent document scrolling
    if (e.key === " " || e.key === "ArrowUp" || e.key === "ArrowDown") {
      e.preventDefault()
    }

  })

  const db = await DB()

  const store = localforage.createInstance({
    name: "SimpleGTD.com offline store",
  });

  const localPref = await store.getItem("local-pref")

  const debugSecondMultiplier = (() => {
    if (WEBPACK_DEV_SERVER) {
      return 300000
    } else {
      return 1
    }
  })()

  const flags = _.merge({
    now: Date.now(),
    pouchDBRemoteSyncURI: localStorage.getItem("pouchdb.remote-sync-uri") || "",
    developmentMode: isDevelopmentMode,
    appVersion: npmPackageVersion,
    deviceId,
    config: {debugSecondMultiplier, deviceId, npmPackageVersion, isDevelopmentMode},
    localPref: localPref,
  }, db.allDocsMap)

  const app = elmMain
      .embed(document.getElementById("elm-container"), flags)

  const fire = Fire.setup(app, _.values(db.list), deviceId)

  db.setupApp(app)

  app.ports["persistLocalPref"].subscribe(async (localPref) => {
    store.setItem("local-pref", localPref)
         .catch(console.error)
  });

  Notifications.setup(fire, app).catch(console.error)

  const portFocusSelectorStream =
      Kefir.stream(emitter => {
        app.ports["focusSelector"].subscribe((selector) => {
          // console.log("port: focusSelector received selector", selector)
          emitter.emit(selector)
        })
      })

  const addMetadataToFocusSelectorStream =
      (stream, streamName) => stream.map(selector => ({streamName, selector}))

  const focusSelectorStreams =
      [addMetadataToFocusSelectorStream(
          portFocusSelectorStream,
          "portFocusSelectorStream",
      ),
       addMetadataToFocusSelectorStream(
           mutationObserverFocusSelectorStream,
           "mutationObserverFocusSelectorStream",
       )]
  Kefir.merge(focusSelectorStreams)
       .spy()
       .debounce(100)
       .observe({
         value(options) {
           const $toFocus = $(options.selector).first()

           console.log("[Kefir] focusSelector", _.merge(options, {
             // dataKey: $toFocus.data("key"),
           }))

           $toFocus.focus()
         },
       })

  app.ports["focusSelectorIfNoFocus"].subscribe((selector) => {
    const $focus = $(":focus, [focused]")
    // console.log($focus, $focus.length)
    if ($focus.length === 0) {
      $(selector).focus()
    }
  })

  app.ports["positionPopupMenu"].subscribe((ofSelector) => {
    requestAnimationFrame(() => {
      const $popup = $("#popup-menu")
      $popup.position({
        my: "right top",
        at: "right top",
        of: $(ofSelector),
        within: ".overlay",
        collision: "fit",
      })
      $popup.find(".auto-focus").first().focus()

      const focusableSelector = ":focusable, input:focusable"
      $popup.on("keydown", focusableSelector, function (e) {
        if (e.key !== "Tab") return
        const $focusable = $popup.find(focusableSelector)

        const first = $focusable.first().get(0)
        const last = $focusable.last().get(0)
        const dateTimeInputSelector = `input[type="date"], input[type="time"]`

        if (e.shiftKey) {

          if (this === first) {
            e.preventDefault()
            e.stopPropagation()
            last.focus()
          } else if ($(this).is(dateTimeInputSelector)) {
            e.preventDefault()
            e.stopPropagation()
            $focusable.get($.inArray(this, $focusable) - 1).focus()
          }
        }
        else {

          if (this === last) {
            e.preventDefault()
            e.stopPropagation()
            first.focus()
          } else if ($(this).is(dateTimeInputSelector)) {
            e.preventDefault()
            e.stopPropagation()
            $focusable.get($.inArray(this, $focusable) + 1).focus()
          }
        }
      })
    })
  })

  app.ports["startAlarm"].subscribe(() => {
    sound.start()
  })

  app.ports["stopAlarm"].subscribe(() => {
    sound.stop()
  })
}

function getOrCreateDeviceId() {
  let deviceId = localStorage.getItem("device-id")
  if (!deviceId) {
    deviceId = cryptoRandomString(64)
    localStorage.setItem("device-id", deviceId)
  }
  return deviceId
}

function getOrCreateFirstVisit() {
  let firstVisit = localStorage.getItem("first-visit")
  if (!firstVisit) {
    localStorage.setItem("first-visit", "false")
  }
  return !!firstVisit
}
<|endoftext|>


<commit_before>/**
 * @copyright 2016-present, Reactdown team
 * @flow
 */

import {renderToString} from './index';
import {
  findConfig,
  mergeConfig,
  parseConfigFromQuery
} from './Config';

module.exports = function reactdown(source: string): string {
  this.cacheable();
  let compiler = this._compiler;
  if (compiler.__reactdownConfig === undefined) {
    compiler.__reactdownConfig = findConfig(compiler.context).config;
  }
  let config = mergeConfig(
    compiler.__reactdownConfig,
    parseConfigFromQuery(this.query)
  );
  return renderToString(source, config).code;
};
<commit_msg>doc(webpack): comment code<commit_after>/**
 * @copyright 2016-present, Reactdown team
 * @flow
 */

import {renderToString} from './index';
import {
  findConfig,
  mergeConfig,
  parseConfigFromQuery
} from './Config';

/**
 * Webpack loader for reactdown documents.
 */
function reactdown(source: string): string {
  this.cacheable();

  let compiler = this._compiler;

  // We read the config once. That means on changes to config one must restart
  // the compiler.
  //
  // TODO: Improve on that, so changes to configration do not require restarting
  // Webpack compiler.
  if (compiler.__reactdownConfig === undefined) {
    compiler.__reactdownConfig = findConfig(compiler.context).config;
  }

  let config = mergeConfig(
    compiler.__reactdownConfig,
    parseConfigFromQuery(this.query)
  );

  return renderToString(source, config).code;
}

module.exports = reactdown;
<|endoftext|>


<commit_before>function modelAccessor(scope, element) {
  var expr = element.attr('name');
  if (!expr) throw "Required field 'name' not found.";
  return {
    get: function() {
      return scope.$eval(expr);
    },
    set: function(value) {
      if (value !== undefined) {
        return scope.$tryEval(expr + '=' + toJson(value), element);
      }
    }
  };
}

function modelFormattedAccessor(scope, element) {
  var accessor = modelAccessor(scope, element),
      formatterName = element.attr('ng-format') || NOOP,
      formatter = angularFormatter(formatterName);
  if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
  return {
    get: function() {
      return formatter.format(accessor.get());
    },
    set: function(value) {
      return accessor.set(formatter.parse(value));
    }
  };
}

function compileValidator(expr) {
  return new Parser(expr).validator()();
}

function valueAccessor(scope, element) {
  var validatorName = element.attr('ng-validate') || NOOP,
      validator = compileValidator(validatorName),
      requiredExpr = element.attr('ng-required'),
      formatterName = element.attr('ng-format') || NOOP,
      formatter = angularFormatter(formatterName),
      format, parse, lastError, required;
      invalidWidgets = scope.$invalidWidgets || {markValid:noop, markInvalid:noop};
  if (!validator) throw "Validator named '" + validatorName + "' not found.";
  if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
  format = formatter.format;
  parse = formatter.parse;
  if (requiredExpr) {
    scope.$watch(requiredExpr, function(newValue) {
      required = newValue;
      validate();
    });
  } else {
    required = requiredExpr === '';
  }

  element.data('$validate', validate);
  return {
    get: function(){
      if (lastError)
        elementError(element, NG_VALIDATION_ERROR, null);
      try {
        var value = parse(element.val());
        validate();
        return value;
      } catch (e) {
        lastError = e;
        elementError(element, NG_VALIDATION_ERROR, e);
      }
    },
    set: function(value) {
      var oldValue = element.val(),
          newValue = format(value);
      if (oldValue != newValue) {
        element.val(newValue || ''); // needed for ie
      }
      validate();
    }
  };

  function validate() {
    var value = trim(element.val());
    if (element[0].disabled || element[0].readOnly) {
      elementError(element, NG_VALIDATION_ERROR, null);
      invalidWidgets.markValid(element);
    } else {
      var error,
          validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element});
      error = required && !value ?
              'Required' :
              value ? validator(validateScope, value) : null;
      elementError(element, NG_VALIDATION_ERROR, error);
      lastError = error;
      if (error) {
        invalidWidgets.markInvalid(element);
      } else {
        invalidWidgets.markValid(element);
      }
    }
  }
}

function checkedAccessor(scope, element) {
  var domElement = element[0], elementValue = domElement.value;
  return {
    get: function(){
      return !!domElement.checked;
    },
    set: function(value){
      domElement.checked = toBoolean(value);
    }
  };
}

function radioAccessor(scope, element) {
  var domElement = element[0];
  return {
    get: function(){
      return domElement.checked ? domElement.value : null;
    },
    set: function(value){
      domElement.checked = value == domElement.value;
    }
  };
}

function optionsAccessor(scope, element) {
  var options = element[0].options;
  return {
    get: function(){
      var values = [];
      foreach(options, function(option){
        if (option.selected) values.push(option.value);
      });
      return values;
    },
    set: function(values){
      var keys = {};
      foreach(values, function(value){ keys[value] = true; });
      foreach(options, function(option){
        option.selected = keys[option.value];
      });
    }
  };
}

function noopAccessor() { return { get: noop, set: noop }; }

var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue()),
    buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
    INPUT_TYPE = {
      'text':            textWidget,
      'textarea':        textWidget,
      'hidden':          textWidget,
      'password':        textWidget,
      'button':          buttonWidget,
      'submit':          buttonWidget,
      'reset':           buttonWidget,
      'image':           buttonWidget,
      'checkbox':        inputWidget('click', modelFormattedAccessor, checkedAccessor, initWidgetValue(false)),
      'radio':           inputWidget('click', modelFormattedAccessor, radioAccessor, radioInit),
      'select-one':      inputWidget('change', modelFormattedAccessor, valueAccessor, initWidgetValue(null)),
      'select-multiple': inputWidget('change', modelFormattedAccessor, optionsAccessor, initWidgetValue([]))
//      'file':            fileWidget???
    };

function initWidgetValue(initValue) {
  return function (model, view) {
    var value = view.get();
    if (!value && isDefined(initValue)) {
      value = copy(initValue);
    }
    if (isUndefined(model.get()) && isDefined(value)) {
      model.set(value);
    }
  };
}

function radioInit(model, view, element) {
 var modelValue = model.get(), viewValue = view.get(), input = element[0];
 input.checked = false;
 input.name = this.$id + '@' + input.name;
 if (isUndefined(modelValue)) {
   model.set(modelValue = null);
 }
 if (modelValue == null && viewValue !== null) {
   model.set(viewValue);
 }
 view.set(modelValue);
}

function inputWidget(events, modelAccessor, viewAccessor, initFn) {
  return function(element) {
    var scope = this,
        model = modelAccessor(scope, element),
        view = viewAccessor(scope, element),
        action = element.attr('ng-change') || '',
        lastValue;
    initFn.call(scope, model, view, element);
    this.$eval(element.attr('ng-init')||'');
    // Don't register a handler if we are a button (noopAccessor) and there is no action
    if (action || modelAccessor !== noopAccessor) {
      element.bind(events, function(){
        model.set(view.get());
        lastValue = model.get();
        scope.$tryEval(action, element);
        scope.$root.$eval();
        // if we have noop initFn than we are just a button,
        // therefore we want to prevent default action
        return initFn != noop;
      });
    }
    view.set(lastValue = model.get());
    scope.$watch(model.get, function(value){
      if (lastValue !== value) {
        view.set(lastValue = value);
      }
    });
  };
}

function inputWidgetSelector(element){
  this.directives(true);
  return INPUT_TYPE[lowercase(element[0].type)] || noop;
}

angularWidget('INPUT', inputWidgetSelector);
angularWidget('TEXTAREA', inputWidgetSelector);
angularWidget('BUTTON', inputWidgetSelector);
angularWidget('SELECT', function(element){
  this.descend(true);
  return inputWidgetSelector.call(this, element);
});

angularWidget('NG:INCLUDE', function(element){
  var compiler = this,
      srcExp = element.attr("src"),
      scopeExp = element.attr("scope") || '';
  if (element[0]['ng-compiled']) {
    this.descend(true);
    this.directives(true);
  } else {
    element[0]['ng-compiled'] = true;
    return function(element){
      var scope = this, childScope;
      var changeCounter = 0;
      function incrementChange(){ changeCounter++;}
      this.$watch(srcExp, incrementChange);
      this.$watch(scopeExp, incrementChange);
      scope.$onEval(function(){
        if (childScope) childScope.$eval();
      });
      this.$watch(function(){return changeCounter;}, function(){
        var src = this.$eval(srcExp),
        useScope = this.$eval(scopeExp);
        if (src) {
          scope.$xhr.cache('GET', src, function(code, response){
            element.html(response);
            childScope = useScope || createScope(scope);
            compiler.compile(element)(element, childScope);
            childScope.$init();
          });
        }
      });
    };
  }
});

var ngSwitch = angularWidget('NG:SWITCH', function (element){
  var compiler = this,
      watchExpr = element.attr("on"),
      usingExpr = (element.attr("using") || 'equals'),
      usingExprParams = usingExpr.split(":"),
      usingFn = ngSwitch[usingExprParams.shift()],
      changeExpr = element.attr('change') || '',
      cases = [];
  if (!usingFn) throw "Using expression '" + usingExpr + "' unknown.";
  eachNode(element, function(caseElement){
    var when = caseElement.attr('ng-switch-when');
    if (when) {
      cases.push({
        when: function(scope, value){
          var args = [value, when];
          foreach(usingExprParams, function(arg){
            args.push(arg);
          });
          return usingFn.apply(scope, args);
        },
        change: changeExpr,
        element: caseElement,
        template: compiler.compile(caseElement)
      });
    }
  });

  // this needs to be here for IE
  foreach(cases, function(_case){
    _case.element.remove();
  });

  element.html('');
  return function(element){
    var scope = this, childScope;
    this.$watch(watchExpr, function(value){
      element.html('');
      childScope = createScope(scope);
      foreach(cases, function(switchCase){
        if (switchCase.when(childScope, value)) {
          var caseElement = switchCase.element.clone();
          element.append(caseElement);
          childScope.$tryEval(switchCase.change, element);
          switchCase.template(caseElement, childScope);
          if (scope.$invalidWidgets)
            scope.$invalidWidgets.clearOrphans();
          childScope.$init();
        }
      });
    });
    scope.$onEval(function(){
      if (childScope) childScope.$eval();
    });
  };
}, {
  equals: function(on, when) {
    return on == when;
  },
  route: switchRouteMatcher
});
<commit_msg>add back nicer formatting<commit_after>function modelAccessor(scope, element) {
  var expr = element.attr('name');
  if (!expr) throw "Required field 'name' not found.";
  return {
    get: function() {
      return scope.$eval(expr);
    },
    set: function(value) {
      if (value !== undefined) {
        return scope.$tryEval(expr + '=' + toJson(value), element);
      }
    }
  };
}

function modelFormattedAccessor(scope, element) {
  var accessor = modelAccessor(scope, element),
      formatterName = element.attr('ng-format') || NOOP,
      formatter = angularFormatter(formatterName);
  if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
  return {
    get: function() {
      return formatter.format(accessor.get());
    },
    set: function(value) {
      return accessor.set(formatter.parse(value));
    }
  };
}

function compileValidator(expr) {
  return new Parser(expr).validator()();
}

function valueAccessor(scope, element) {
  var validatorName = element.attr('ng-validate') || NOOP,
      validator = compileValidator(validatorName),
      requiredExpr = element.attr('ng-required'),
      formatterName = element.attr('ng-format') || NOOP,
      formatter = angularFormatter(formatterName),
      format, parse, lastError, required;
      invalidWidgets = scope.$invalidWidgets || {markValid:noop, markInvalid:noop};
  if (!validator) throw "Validator named '" + validatorName + "' not found.";
  if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
  format = formatter.format;
  parse = formatter.parse;
  if (requiredExpr) {
    scope.$watch(requiredExpr, function(newValue) {
      required = newValue;
      validate();
    });
  } else {
    required = requiredExpr === '';
  }

  element.data('$validate', validate);
  return {
    get: function(){
      if (lastError)
        elementError(element, NG_VALIDATION_ERROR, null);
      try {
        var value = parse(element.val());
        validate();
        return value;
      } catch (e) {
        lastError = e;
        elementError(element, NG_VALIDATION_ERROR, e);
      }
    },
    set: function(value) {
      var oldValue = element.val(),
          newValue = format(value);
      if (oldValue != newValue) {
        element.val(newValue || ''); // needed for ie
      }
      validate();
    }
  };

  function validate() {
    var value = trim(element.val());
    if (element[0].disabled || element[0].readOnly) {
      elementError(element, NG_VALIDATION_ERROR, null);
      invalidWidgets.markValid(element);
    } else {
      var error,
          validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element});
      error = required && !value ?
              'Required' :
              (value ? validator(validateScope, value) : null);
      elementError(element, NG_VALIDATION_ERROR, error);
      lastError = error;
      if (error) {
        invalidWidgets.markInvalid(element);
      } else {
        invalidWidgets.markValid(element);
      }
    }
  }
}

function checkedAccessor(scope, element) {
  var domElement = element[0], elementValue = domElement.value;
  return {
    get: function(){
      return !!domElement.checked;
    },
    set: function(value){
      domElement.checked = toBoolean(value);
    }
  };
}

function radioAccessor(scope, element) {
  var domElement = element[0];
  return {
    get: function(){
      return domElement.checked ? domElement.value : null;
    },
    set: function(value){
      domElement.checked = value == domElement.value;
    }
  };
}

function optionsAccessor(scope, element) {
  var options = element[0].options;
  return {
    get: function(){
      var values = [];
      foreach(options, function(option){
        if (option.selected) values.push(option.value);
      });
      return values;
    },
    set: function(values){
      var keys = {};
      foreach(values, function(value){ keys[value] = true; });
      foreach(options, function(option){
        option.selected = keys[option.value];
      });
    }
  };
}

function noopAccessor() { return { get: noop, set: noop }; }

var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue()),
    buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
    INPUT_TYPE = {
      'text':            textWidget,
      'textarea':        textWidget,
      'hidden':          textWidget,
      'password':        textWidget,
      'button':          buttonWidget,
      'submit':          buttonWidget,
      'reset':           buttonWidget,
      'image':           buttonWidget,
      'checkbox':        inputWidget('click', modelFormattedAccessor, checkedAccessor, initWidgetValue(false)),
      'radio':           inputWidget('click', modelFormattedAccessor, radioAccessor, radioInit),
      'select-one':      inputWidget('change', modelFormattedAccessor, valueAccessor, initWidgetValue(null)),
      'select-multiple': inputWidget('change', modelFormattedAccessor, optionsAccessor, initWidgetValue([]))
//      'file':            fileWidget???
    };

function initWidgetValue(initValue) {
  return function (model, view) {
    var value = view.get();
    if (!value && isDefined(initValue)) {
      value = copy(initValue);
    }
    if (isUndefined(model.get()) && isDefined(value)) {
      model.set(value);
    }
  };
}

function radioInit(model, view, element) {
 var modelValue = model.get(), viewValue = view.get(), input = element[0];
 input.checked = false;
 input.name = this.$id + '@' + input.name;
 if (isUndefined(modelValue)) {
   model.set(modelValue = null);
 }
 if (modelValue == null && viewValue !== null) {
   model.set(viewValue);
 }
 view.set(modelValue);
}

function inputWidget(events, modelAccessor, viewAccessor, initFn) {
  return function(element) {
    var scope = this,
        model = modelAccessor(scope, element),
        view = viewAccessor(scope, element),
        action = element.attr('ng-change') || '',
        lastValue;
    initFn.call(scope, model, view, element);
    this.$eval(element.attr('ng-init')||'');
    // Don't register a handler if we are a button (noopAccessor) and there is no action
    if (action || modelAccessor !== noopAccessor) {
      element.bind(events, function(){
        model.set(view.get());
        lastValue = model.get();
        scope.$tryEval(action, element);
        scope.$root.$eval();
        // if we have noop initFn than we are just a button,
        // therefore we want to prevent default action
        return initFn != noop;
      });
    }
    view.set(lastValue = model.get());
    scope.$watch(model.get, function(value){
      if (lastValue !== value) {
        view.set(lastValue = value);
      }
    });
  };
}

function inputWidgetSelector(element){
  this.directives(true);
  return INPUT_TYPE[lowercase(element[0].type)] || noop;
}

angularWidget('INPUT', inputWidgetSelector);
angularWidget('TEXTAREA', inputWidgetSelector);
angularWidget('BUTTON', inputWidgetSelector);
angularWidget('SELECT', function(element){
  this.descend(true);
  return inputWidgetSelector.call(this, element);
});

angularWidget('NG:INCLUDE', function(element){
  var compiler = this,
      srcExp = element.attr("src"),
      scopeExp = element.attr("scope") || '';
  if (element[0]['ng-compiled']) {
    this.descend(true);
    this.directives(true);
  } else {
    element[0]['ng-compiled'] = true;
    return function(element){
      var scope = this, childScope;
      var changeCounter = 0;
      function incrementChange(){ changeCounter++;}
      this.$watch(srcExp, incrementChange);
      this.$watch(scopeExp, incrementChange);
      scope.$onEval(function(){
        if (childScope) childScope.$eval();
      });
      this.$watch(function(){return changeCounter;}, function(){
        var src = this.$eval(srcExp),
        useScope = this.$eval(scopeExp);
        if (src) {
          scope.$xhr.cache('GET', src, function(code, response){
            element.html(response);
            childScope = useScope || createScope(scope);
            compiler.compile(element)(element, childScope);
            childScope.$init();
          });
        }
      });
    };
  }
});

var ngSwitch = angularWidget('NG:SWITCH', function (element){
  var compiler = this,
      watchExpr = element.attr("on"),
      usingExpr = (element.attr("using") || 'equals'),
      usingExprParams = usingExpr.split(":"),
      usingFn = ngSwitch[usingExprParams.shift()],
      changeExpr = element.attr('change') || '',
      cases = [];
  if (!usingFn) throw "Using expression '" + usingExpr + "' unknown.";
  eachNode(element, function(caseElement){
    var when = caseElement.attr('ng-switch-when');
    if (when) {
      cases.push({
        when: function(scope, value){
          var args = [value, when];
          foreach(usingExprParams, function(arg){
            args.push(arg);
          });
          return usingFn.apply(scope, args);
        },
        change: changeExpr,
        element: caseElement,
        template: compiler.compile(caseElement)
      });
    }
  });

  // this needs to be here for IE
  foreach(cases, function(_case){
    _case.element.remove();
  });

  element.html('');
  return function(element){
    var scope = this, childScope;
    this.$watch(watchExpr, function(value){
      element.html('');
      childScope = createScope(scope);
      foreach(cases, function(switchCase){
        if (switchCase.when(childScope, value)) {
          var caseElement = switchCase.element.clone();
          element.append(caseElement);
          childScope.$tryEval(switchCase.change, element);
          switchCase.template(caseElement, childScope);
          if (scope.$invalidWidgets)
            scope.$invalidWidgets.clearOrphans();
          childScope.$init();
        }
      });
    });
    scope.$onEval(function(){
      if (childScope) childScope.$eval();
    });
  };
}, {
  equals: function(on, when) {
    return on == when;
  },
  route: switchRouteMatcher
});
<|endoftext|>


<commit_before>//jshint strict: false
exports.config = {

  allScriptsTimeout: 11000,

  specs: [
    '*.js'
  ],

  capabilities: {
    'browserName': 'chrome'
  },

  baseUrl: 'http://localhost:8000/',

  framework: 'jasmine',

  jasmineNodeOpts: {
    defaultTimeoutInterval: 30000
  }

};
<commit_msg>modify protractor<commit_after>//jshint strict: false
exports.config = {

  allScriptsTimeout: 11000,

  specs: [
    '*.js'
  ],

  capabilities: {
    'browserName': 'chrome'
  },

  chromeOnly: true,

  baseUrl: 'http://localhost:8000/',

  framework: 'jasmine',

  jasmineNodeOpts: {
    defaultTimeoutInterval: 30000
  }

};
<|endoftext|>


<commit_before>var name="Unused variables",
	description="Look for variables that were defined but not referenced";

// This plugin does **NOT** check for variables that are used incorrectly.
// TODO: FaultRules

// basic regex for finding variables
// TODO: handle objects -- right now just verify the highest level variable.
var varFinder = /{([^}\.]+?)(\.[^}]+?)??}/g;

// preload the symbol table with framework globals
var symtab = {
	"proxy":[ "Edge Global" ],
	"request": [ "Edge Global" ],
	"response": [ "Edge Global" ]
};

// errors that will be reported at the end of the process
var errors = [];

// warnings that will be reported at the end of the process
var warnings = [];

// Process proxyendpoints in the following order:
// 1. preflow - request [steps]
// 2. flow - request [steps]
// 3. postflow - request [steps]
// 4. routerules
//    - targetendpoint
//       . pre/flow/post
//       . pre - response
//       . flow - response
//       . post - response
// TODO: inspect things referenced by steps to populate symbol table and check for bad var references.
var onBundle = function (bundle) {
	bundle.getProxyEndpoints().forEach(function(endpoint){
		endpoint.getPreFlow().getFlowRequest().getSteps().forEach( function(step) {
			if( step.getName()) {
				var badVars = getUndefinedVariables(step.getName());
				badVars.forEach(function(badvar){
					step.err("Variable {" + badvar + "} was used in step name, but not previously defined");
				});
			} else {
				step.warn("Step in preFlow does not seem to call anything (empty <Name> node)");
			}
			if( step.getCondition()) {
				var badVars = getUndefinedVariables(step.getCondition().getExpression());
				badVars.forEach(function(badvar){
					step.err("Variable {" + badvar + "} was used in step condition, but not previously defined");
				});
			}
		});
	});
}

// return a list of variables referenced in this string
// that are undefined (may be empty)
var getUndefinedVariables = function (value) {
	var undefinedVars = [];
	while((match = varFinder.exec(value)) != null) {
		var variable = match[1];
		if( ! (variable in symtab)) {
			undefinedVars.push(variable)
		}
	}
	return undefinedVars;
}

module.exports = {
	name,
	description,
	onBundle
};<commit_msg>evaluating all the steps in the right order now (I think -- needs more tests), but still need to populate the symbol table<commit_after>var name="Unused variables",
	description="Look for variables that were defined but not referenced";

// This plugin does **NOT** check for variables that are used incorrectly.
// TODO: FaultRules

// basic regex for finding variables
// TODO: handle objects -- right now just verify the highest level variable.
var varFinder = /{([^}\.]+?)(\.[^}]+?)??}/g;

// preload the symbol table with framework globals
var globalSymtab = [
	"proxy",
	"request",
	"response"
];

// errors that will be reported at the end of the process
var errors = [];

// warnings that will be reported at the end of the process
var warnings = [];

// Process proxyendpoints in the following order:
// 1. preflow - request [steps]
// 2. flow - request [steps]
// 3. postflow - request [steps]
// 4. routerules
//    - targetendpoint
//       . pre/flow/post
//       . pre - response
//       . flow - response
//       . post - response
// TODO: inspect things referenced by steps to populate symbol table and check for bad var references.
var onBundle = function (bundle) {
	bundle.getProxyEndpoints().forEach(function(endpoint){
		var localSymtab = [];
		evaluateSteps(endpoint.getPreFlow().getFlowRequest().getSteps(), localSymtab);
		endpoint.getFlows().forEach(function(flow) {
			evaluateSteps(flow.getFlowRequest().getSteps(), localSymtab);
		});
		evaluateSteps(endpoint.getPostFlow().getFlowRequest().getSteps(), localSymtab);

		var intermediateSymtab = localSymtab.slice(0); // don't update orig array references!
		// iterate through routerules now ...
		bundle.getTargetEndpoints().forEach(function(target){
			localSymtab = intermediateSymtab.slice(0); // reset the local symbol table

			// evaluate the target's inbound request flow(s)
			evaluateSteps(target.getPreFlow().getFlowRequest().getSteps(), localSymtab)
			target.getFlows().forEach(function(flow){
				evaluateSteps(flow.getFlowRequest().getSteps(), localSymtab);
			});
			evaluateSteps(target.getPostFlow().getFlowRequest().getSteps(), localSymtab);

			// assume the target was called, evalute the response flow(s)
			evaluateSteps(target.getPreFlow().getFlowResponse().getSteps(), localSymtab)
			target.getFlows().forEach(function(flow){
				evaluateSteps(flow.getFlowResponse().getSteps(), localSymtab);
			});
			evaluateSteps(target.getPostFlow().getFlowResponse().getSteps(), localSymtab);

			// finally, evalute the response flow(s) for the endpoint with this set of
			// 	local symbols...
			evaluateSteps(endpoint.getPreFlow().getFlowResponse().getSteps(), localSymtab)
			endpoint.getFlows().forEach(function(flow){
				evaluateSteps(flow.getFlowResponse().getSteps(), localSymtab);
			});
			evaluateSteps(endpoint.getPostFlow().getFlowResponse().getSteps(), localSymtab);
		});
	});
}

// TODO: evalute where symbols are added, and record them in our symbol table.
var evaluateSteps = function(steps, localSymtab) {
	steps.forEach( function(step) {
		if( step.getName()) {
			var badVars = getUndefinedVariables(step.getName(), localSymtab);
			badVars.forEach(function(badvar){
				step.err("Variable {" + badvar + "} was used in step name, but not previously defined");
			});
		} else {
			step.warn("Step does not seem to call anything (empty <Name> node)");
		}
		if( step.getCondition()) {
			var badVars = getUndefinedVariables(step.getCondition().getExpression());
			badVars.forEach(function(badvar){
				step.err("Variable {" + badvar + "} was used in step condition, but not previously defined");
			});
		}
	});
}

// return a list of variables referenced in this string
// that are undefined (may be empty)
var getUndefinedVariables = function (value, localSymtab) {
	var symtab = globalSymtab.concat(localSymtab);
	var undefinedVars = [];
	while((match = varFinder.exec(value)) != null) {
		var variable = match[1];
		if( ! (variable in symtab)) {
			undefinedVars.push(variable)
		}
	}
	return undefinedVars;
}

module.exports = {
	name,
	description,
	onBundle
};<|endoftext|>


<commit_before>/*
Language: Python
*/

hljs.LANGUAGES.python = {
  defaultMode: {
    lexems: [hljs.UNDERSCORE_IDENT_RE],
    illegal: '(</|->)',
    contains: ['comment', 'string', 'function', 'class', 'number', 'decorator'],
    keywords: {
      'keyword': {'and': 1, 'elif': 1, 'is': 1, 'global': 1, 'as': 1, 'in': 1, 'if': 1, 'from': 1, 'raise': 1, 'for': 1, 'except': 1, 'finally': 1, 'print': 1, 'import': 1, 'pass': 1, 'return': 1, 'exec': 1, 'else': 1, 'break': 1, 'not': 1, 'with': 1, 'class': 1, 'assert': 1, 'yield': 1, 'try': 1, 'while': 1, 'continue': 1, 'del': 1, 'or': 1, 'def': 1, 'lambda': 1, 'nonlocal': 10},
      'built_in': {'None': 1, 'True': 1, 'False': 1, 'Ellipsis': 1, 'NotImplemented': 1}
    }
  },
  modes: [
    {
      className: 'function',
      lexems: [hljs.UNDERSCORE_IDENT_RE],
      begin: '\\bdef ', end: ':',
      illegal: '$',
      keywords: {'def': 1},
      contains: ['title', 'params'],
      relevance: 10
    },
    {
      className: 'class',
      lexems: [hljs.UNDERSCORE_IDENT_RE],
      begin: '\\bclass ', end: ':',
      illegal: '[${]',
      keywords: {'class': 1},
      contains: ['title', 'params',],
      relevance: 10
    },
    {
      className: 'title',
      begin: hljs.UNDERSCORE_IDENT_RE, end: '^'
    },
    {
      className: 'params',
      begin: '\\(', end: '\\)',
      contains: ['string']
    },
    hljs.HASH_COMMENT_MODE,
    hljs.C_NUMBER_MODE,
    {
      className: 'string',
      begin: 'u?r?\'\'\'', end: '\'\'\'',
      relevance: 10
    },
    {
      className: 'string',
      begin: 'u?r?"""', end: '"""',
      relevance: 10
    },
    hljs.APOS_STRING_MODE,
    hljs.QUOTE_STRING_MODE,
    hljs.BACKSLASH_ESCAPE,
    {
      className: 'string',
      begin: '(u|r|ur)\'', end: '\'',
      contains: ['escape'],
      relevance: 10
    },
    {
      className: 'string',
      begin: '(u|r|ur)"', end: '"',
      contains: ['escape'],
      relevance: 10
    },
    {
      className: 'decorator',
      begin: '@', end: '$'
    }
  ]
};
<commit_msg>Fixed trailing comma in python.js<commit_after>/*
Language: Python
*/

hljs.LANGUAGES.python = {
  defaultMode: {
    lexems: [hljs.UNDERSCORE_IDENT_RE],
    illegal: '(</|->)',
    contains: ['comment', 'string', 'function', 'class', 'number', 'decorator'],
    keywords: {
      'keyword': {'and': 1, 'elif': 1, 'is': 1, 'global': 1, 'as': 1, 'in': 1, 'if': 1, 'from': 1, 'raise': 1, 'for': 1, 'except': 1, 'finally': 1, 'print': 1, 'import': 1, 'pass': 1, 'return': 1, 'exec': 1, 'else': 1, 'break': 1, 'not': 1, 'with': 1, 'class': 1, 'assert': 1, 'yield': 1, 'try': 1, 'while': 1, 'continue': 1, 'del': 1, 'or': 1, 'def': 1, 'lambda': 1, 'nonlocal': 10},
      'built_in': {'None': 1, 'True': 1, 'False': 1, 'Ellipsis': 1, 'NotImplemented': 1}
    }
  },
  modes: [
    {
      className: 'function',
      lexems: [hljs.UNDERSCORE_IDENT_RE],
      begin: '\\bdef ', end: ':',
      illegal: '$',
      keywords: {'def': 1},
      contains: ['title', 'params'],
      relevance: 10
    },
    {
      className: 'class',
      lexems: [hljs.UNDERSCORE_IDENT_RE],
      begin: '\\bclass ', end: ':',
      illegal: '[${]',
      keywords: {'class': 1},
      contains: ['title', 'params'],
      relevance: 10
    },
    {
      className: 'title',
      begin: hljs.UNDERSCORE_IDENT_RE, end: '^'
    },
    {
      className: 'params',
      begin: '\\(', end: '\\)',
      contains: ['string']
    },
    hljs.HASH_COMMENT_MODE,
    hljs.C_NUMBER_MODE,
    {
      className: 'string',
      begin: 'u?r?\'\'\'', end: '\'\'\'',
      relevance: 10
    },
    {
      className: 'string',
      begin: 'u?r?"""', end: '"""',
      relevance: 10
    },
    hljs.APOS_STRING_MODE,
    hljs.QUOTE_STRING_MODE,
    hljs.BACKSLASH_ESCAPE,
    {
      className: 'string',
      begin: '(u|r|ur)\'', end: '\'',
      contains: ['escape'],
      relevance: 10
    },
    {
      className: 'string',
      begin: '(u|r|ur)"', end: '"',
      contains: ['escape'],
      relevance: 10
    },
    {
      className: 'decorator',
      begin: '@', end: '$'
    }
  ]
};
<|endoftext|>