<commit_before>/*
 * Parse .ipuz crossword files.
 */
function Ipuz(data) {

  this.FLAGS = {
    INCORRECT_ONCE: 0x10,
    INCORRECT: 0x20,
    REVEALED: 0x40,
    CIRCLED: 0x80,
    HIDDEN: 0x100,
    SHADED: 0x200,
  };

  this.headers = [];  // list of [header, value]
  this.grid = [];     // [y][x] 2-dim array
  this.flags = [];     // [y][x] 2-dim array
  this.clues = [];    // list of [[direction, num], clue, answer]
  this.notes = "";
  this.number_index = [];
  this.rebus_key = {};
  this.width = 0;
  this.height = 0;
  this.BLOCK = "#";

  var self = this;

  this.parseIpuz = function(json) {

    const ipuz = JSON.parse(json);

    const dimensions = ipuz.dimensions;
    const width = parseInt(dimensions.width);
    const height = parseInt(dimensions.height);

    self.width = width;
    self.height = height;

    const puzzle = ipuz.puzzle;
    const solution = ipuz.solution;
    const acrossClues = ipuz.clues.Across;
    const downClues = ipuz.clues.Down;

    for (var i = 0; i < height; i++) {
        var flagsline = Array(width).fill(0);
        var gridline = Array(width);

        var puzzleRow = puzzle[i];
        var solutionRow = solution[i];

        for (var j = 0; j < width; j++) {
            gridline[j] = solutionRow[j] || " ";
            if (puzzleRow[j] === this.BLOCK) {
                gridline[j] = this.BLOCK;
            }

            if (puzzleRow[j] == null) {
                flagsline[j] |= self.FLAGS.HIDDEN;
            }
            if (puzzleRow[j].hasOwnProperty('style')) {
                if (puzzleRow[j]['style'].get('shapebg') === 'circle') {
                    flagsline[j] |= self.FLAGS.CIRCLED;
                }
            }
            const cell = parseInt(puzzleRow[j].cell || puzzleRow[j]);
            if (cell > 0) {
                self.number_index[cell] = [j, i];
            }
        }

        self.grid.push(gridline);
        self.flags.push(flagsline);
    }

    for (var i = 0; i < acrossClues.length; i++) {
        self.addClue('A', acrossClues[i]);
    }

    for (var i = 0; i < downClues.length; i++) {
        self.addClue('D', downClues[i]);
    }
  }

  this.addClue = function(direction, numClue) {
    var num;
    var clue;

    if (numClue.hasOwnProperty('number')) {
        num = numClue.number;
        clue = numClue.clue;
    } else {
        num = parseInt(numClue[0]);
        clue = numClue[1];
    }

    const xy = self.number_index[num];
    self.clues.push([[direction, num], clue,
                    self.getAnswer(direction, xy[0], xy[1])]);
  }

  this.getAnswer = function(direction, x, y) {
    var xinc = direction === 'A' ? 1 : 0;
    var yinc = direction === 'D' ? 1 : 0;

    var str = '';
    while (y < self.height && x < self.width &&
           self.grid[y][x] !== this.BLOCK &&
           !(self.flags[y][x] & self.FLAGS.HIDDEN)) {
      str += self.grid[y][x];
      y += yinc;
      x += xinc;
    }
    return str;
  }

  self.parseIpuz(data);
}

if (typeof(module) !== "undefined") {
  module.exports = Ipuz;
}
<commit_msg>Add author/title fields for ipuz & drop self/this cargo cult<commit_after>/*
 * Parse .ipuz crossword files.
 */
function Ipuz(data) {

  this.FLAGS = {
    INCORRECT_ONCE: 0x10,
    INCORRECT: 0x20,
    REVEALED: 0x40,
    CIRCLED: 0x80,
    HIDDEN: 0x100,
    SHADED: 0x200,
  };

  this.headers = [];  // list of [header, value]
  this.grid = [];     // [y][x] 2-dim array
  this.flags = [];     // [y][x] 2-dim array
  this.clues = [];    // list of [[direction, num], clue, answer]
  this.notes = "";
  this.number_index = [];
  this.rebus_key = {};
  this.width = 0;
  this.height = 0;
  this.BLOCK = "#";

  this.parseIpuz = function(json) {

    const ipuz = JSON.parse(json);

    const dimensions = ipuz.dimensions;
    const width = parseInt(dimensions.width);
    const height = parseInt(dimensions.height);

    this.width = width;
    this.height = height;

    const puzzle = ipuz.puzzle;
    const solution = ipuz.solution;
    const acrossClues = ipuz.clues.Across;
    const downClues = ipuz.clues.Down;

    this.headers = [
        ['Author', ipuz.author],
        ['Title', ipuz.title]
    ];

    for (var i = 0; i < height; i++) {
        var flagsline = Array(width).fill(0);
        var gridline = Array(width);

        var puzzleRow = puzzle[i];
        var solutionRow = solution[i];

        for (var j = 0; j < width; j++) {
            gridline[j] = solutionRow[j] || " ";
            if (puzzleRow[j] === this.BLOCK) {
                gridline[j] = this.BLOCK;
            }

            if (puzzleRow[j] == null) {
                flagsline[j] |= this.FLAGS.HIDDEN;
            }
            if (puzzleRow[j].hasOwnProperty('style')) {
                if (puzzleRow[j]['style'].get('shapebg') === 'circle') {
                    flagsline[j] |= this.FLAGS.CIRCLED;
                }
            }
            const cell = parseInt(puzzleRow[j].cell || puzzleRow[j]);
            if (cell > 0) {
                this.number_index[cell] = [j, i];
            }
        }

        this.grid.push(gridline);
        this.flags.push(flagsline);
    }

    for (var i = 0; i < acrossClues.length; i++) {
        this.addClue('A', acrossClues[i]);
    }

    for (var i = 0; i < downClues.length; i++) {
        this.addClue('D', downClues[i]);
    }
  }

  this.addClue = function(direction, numClue) {
    var num;
    var clue;

    if (numClue.hasOwnProperty('number')) {
        num = numClue.number;
        clue = numClue.clue;
    } else {
        num = parseInt(numClue[0]);
        clue = numClue[1];
    }

    const xy = this.number_index[num];
    this.clues.push([[direction, num], clue,
                    this.getAnswer(direction, xy[0], xy[1])]);
  }

  this.getAnswer = function(direction, x, y) {
    var xinc = direction === 'A' ? 1 : 0;
    var yinc = direction === 'D' ? 1 : 0;

    var str = '';
    while (y < this.height && x < this.width &&
           this.grid[y][x] !== this.BLOCK &&
           !(this.flags[y][x] & this.FLAGS.HIDDEN)) {
      str += this.grid[y][x];
      y += yinc;
      x += xinc;
    }
    return str;
  }

  this.parseIpuz(data);
}

if (typeof(module) !== "undefined") {
  module.exports = Ipuz;
}
<|endoftext|>


<commit_before>const spawn = require('child_process').spawn;
const path = require('path');
const log = require('npmlog');
const uniq = require('lodash.uniq');
const async = require('async');

const isEmpty = require('./isEmpty');
const registerDependencyAndroid = require('./android/registerNativeModule');
const registerDependencyIOS = require('./ios/registerNativeModule');
const copyAssetsAndroid = require('./android/copyAssets');
const copyAssetsIOS = require('./ios/copyAssets');

log.heading = 'rnpm-link';

const makeHook = (dependency, name) => (cb) => {
  if (!dependency.config.hooks || !dependency.config.hooks[name]) {
    return cb();
  }

  const hook = spawn(dependency.config.hooks[name], {
    stdio: 'inherit',
    stdin: 'inherit',
  });

  hook.on('close', function prelink(code) {
    if (code) {
      log.error(`Failed to link a "${dependency.name}" module`);
    }

    cb();
  });
};

/**
 * Returns an array of dependencies that should be linked/checked.
 */
const getProjectDependencies = () => {
  const pjson = require(path.join(process.cwd(), './package.json'));
  return Object.keys(pjson.dependencies).filter(name => name !== 'react-native');
};

/**
 * Updates project and linkes all dependencies to it
 *
 * If optional argument [packageName] is provided, it's the only one that's checked
 */
module.exports = function link(config, args, callback) {

  try {
    const project = config.getProjectConfig();
  } catch (err) {
    log.error('ERRPACKAGEJSON', `No package found. Are you sure it's a React Native project?`);
    return;
  }

  const packageName = args[0];

  const dependencies =
    (packageName ? [packageName] : getProjectDependencies())
    .map(name => {
      try {
        return {
          config: config.getDependencyConfig(name),
          name,
        };
      } catch (err) {
        log.warn(
          'ERRINVALIDPROJ',
          `Project ${name} is not a react-native library`
        );
      }
    })
    .filter(dependency => dependency);

  const assets = uniq(
    dependencies.reduce(
      (assets, dependency) => assets.concat(dependency.config.assets),
      project.assets
    ),
    asset => path.basename(asset)
  );

  const makeLink = (dependency) => (cb) => {
    if (project.android && dependency.config.android) {
      log.info(`Linking ${dependency.name} android dependency`);
      registerDependencyAndroid(
        dependency.name,
        dependency.config.android,
        project.android
      );
    }

    if (project.ios && dependency.config.ios) {
      log.info(`Linking ${dependency.name} ios dependency`);
      registerDependencyIOS(dependency.config.ios, project.ios);
    }

    if (isEmpty(assets)) {
      return cb();
    }

    if (project.ios) {
      log.info('Linking assets to ios project');
      copyAssetsIOS(assets, project.ios);
    }

    if (project.android) {
      log.info('Linking assets to android project');
      copyAssetsAndroid(assets, project.android.assetsPath);
    }

    cb();
  };

  const tasks = dependencies.map((dependency) => (next) => {
    const prelink = makeHook(dependency, 'prelink');
    const postlink = makeHook(dependency, 'postlink');
    const link = makeLink(dependency);

    async.waterfall([prelink, link, postlink, next]);
  });

  async.series(tasks, callback || () => {});
};
<commit_msg>Better error reporting<commit_after>const spawn = require('child_process').spawn;
const path = require('path');
const log = require('npmlog');
const uniq = require('lodash.uniq');
const async = require('async');

const isEmpty = require('./isEmpty');
const registerDependencyAndroid = require('./android/registerNativeModule');
const registerDependencyIOS = require('./ios/registerNativeModule');
const copyAssetsAndroid = require('./android/copyAssets');
const copyAssetsIOS = require('./ios/copyAssets');

log.heading = 'rnpm-link';

const makeHook = (dependency, name) => (cb) => {
  if (!dependency.config.hooks || !dependency.config.hooks[name]) {
    return cb();
  }

  const hook = spawn(dependency.config.hooks[name], {
    stdio: 'inherit',
    stdin: 'inherit',
  });

  hook.on('close', function prelink(code) {
    if (code) {
      cb(new Error(`Failed to link a "${dependency.name}" module`));
    }

    cb();
  });
};

/**
 * Returns an array of dependencies that should be linked/checked.
 */
const getProjectDependencies = () => {
  const pjson = require(path.join(process.cwd(), './package.json'));
  return Object.keys(pjson.dependencies).filter(name => name !== 'react-native');
};

/**
 * Updates project and linkes all dependencies to it
 *
 * If optional argument [packageName] is provided, it's the only one that's checked
 */
module.exports = function link(config, args, callback) {

  try {
    const project = config.getProjectConfig();
  } catch (err) {
    log.error('ERRPACKAGEJSON', `No package found. Are you sure it's a React Native project?`);
    return;
  }

  const packageName = args[0];

  const dependencies =
    (packageName ? [packageName] : getProjectDependencies())
    .map(name => {
      try {
        return {
          config: config.getDependencyConfig(name),
          name,
        };
      } catch (err) {
        log.warn(
          'ERRINVALIDPROJ',
          `Project ${name} is not a react-native library`
        );
      }
    })
    .filter(dependency => dependency);

  const assets = uniq(
    dependencies.reduce(
      (assets, dependency) => assets.concat(dependency.config.assets),
      project.assets
    ),
    asset => path.basename(asset)
  );

  const makeLink = (dependency) => (cb) => {
    if (project.android && dependency.config.android) {
      log.info(`Linking ${dependency.name} android dependency`);
      registerDependencyAndroid(
        dependency.name,
        dependency.config.android,
        project.android
      );
    }

    if (project.ios && dependency.config.ios) {
      log.info(`Linking ${dependency.name} ios dependency`);
      registerDependencyIOS(dependency.config.ios, project.ios);
    }

    if (isEmpty(assets)) {
      return cb();
    }

    if (project.ios) {
      log.info('Linking assets to ios project');
      copyAssetsIOS(assets, project.ios);
    }

    if (project.android) {
      log.info('Linking assets to android project');
      copyAssetsAndroid(assets, project.android.assetsPath);
    }

    cb();
  };

  const tasks = dependencies.map((dependency) => (next) => {
    const prelink = makeHook(dependency, 'prelink');
    const postlink = makeHook(dependency, 'postlink');
    const link = makeLink(dependency);

    async.waterfall([prelink, link, postlink, next]);
  });

  async.series(tasks, callback || () => {});
};
<|endoftext|>


<commit_before>'use strict';

var fnTrue = () => true;
var fnSelf = x => x;

let enumerableSymbols = {
    arr: Symbol(),
    index: Symbol()
};

class Enumerable {
    constructor(arr = []) {
        this[enumerableSymbols.arr] = arr;
        this[enumerableSymbols.index] = 0;
    }

    [Symbol.iterator]() {
        return this;
    }

    next() {
        if (this[enumerableSymbols.index] < this[enumerableSymbols.arr].length) {
            return {
                value: this[enumerableSymbols.arr][this[enumerableSymbols.index]++],
                done: false
            };
        }

        return {
            done: true
        };
    }

    first(selector = fnTrue) {
        for (let item of this) {
            if (selector(item)) {
                return item;
            }
        }

        throw new Error('Sequence contains no matching elements');
    }

    firstOrDefault(selector = fnTrue) {
        try {
            return this.first(selector);
        } catch (e) {
            return undefined;
        }
    }

    single(seletor = fnTrue) {
        var matched;
        for (let item of this) {
            if (fn(item)) {
                if (matched) {
                    throw Error('Sequence contains more than one matching element');
                } else {
                    matched = item;
                }
            }
        }
        if (matched) {
            return matched;
        }

        throw Error('Sequence contains no matching element');
    }

    singleOrDefault(selector = fnTrue) {
        try {
            return this.single(selector);
        } catch (e) {
            if (e.message === 'Sequence contains no matching elements') {
                return undefined;
            }
            throw e;
        }
    }

    all(fn = fnTrue) {
        for (let x of this) {
            if (!fn(x)) {
                return false;
            }
        }
        return true;
    }

    any(fn = fnTrue) {
        for (let x of this) {
            if (fn(x)) {
                return true;
            }
        }
        return false;
    }

    count(fn = fnTrue) {
        var count = 0;
        for (let i of this) {
            if (fn(i)) {
                count++;
            }
        }
        return count;
    }

    aggregate(seed, fn, selector) {
        var it = this;

        switch (arguments.length) {
            case 1:
                fn = seed;
                seed = it.next().value;
                break;

            case 2:
                if (typeof seed === 'function') {
                    selector = fn;
                    fn = seed;
                    seed = it.next().value;
                }
                break;
        }
        for (let item of it) {
            seed = fn(seed, item);
        }

        if (selector) {
            return selector(seed);
        }
        return seed;
    }

    average(fn = fnSelf) {
        var total = 0;
        var count = 0;

        for (let item of this) {
            total += fn(item);
            count++;
        }

        if (count) {
            return total / count;
        }

        throw Error('Sequence contains no elements');
    }

    concat(col) {
        return new ConcatEnumerable(this, col);
    }

    contains(value, tester = x => x === value) {
        var it = this;

        for (let item of it) {
            if (tester(item, value)) {
                return true;
            }
        }
        return false;
    }

    where(selector) {
        return new WhereEnumerable(this, selector);
    }

    select(fn = fnSelf) {
        return new SelectEnumerable(this, fn);
    }

    map(fn) {
        return this.select(fn);
    }

    selectMany(fn = fnSelf) {
        return new SelectManyEnumerable(this, fn);
    }

    take(count = 0) {
        return new TakeEnumerable(this, count);
    }

    takeWhile(fn = 0) {
        if (fn === 0) {
            return this.take();
        }
        return new TakeEnumerable(this, fn);
    }

    skip(count = 0) {
        return new SkipEnumerable(this, count);
    }

    skipWhile(fn = 0) {
        if (fn === 0) {
            return this.skip();
        }
        return new SkipEnumerable(this, fn);
    }

    toArray() {
        var arr = [];
        for (let i of this) {
            arr.push(i);
        }
        return arr;
    }

    static range(start = 0, end = 0) {
        return new RangeEnumerable(start, end);
    }

    static repeat(item, count = 0) {
        return new RepeatEnumerable(item, count);
    }
}

class ConcatEnumerable extends Enumerable {
    constructor(arr, col) {
        super();
        this.arr = arr;
        this.col = Array.isArray(col) ? col.asEnumerable() : col;
    }

    next() {
        var value = this.arr.next();
        if (!value.done) {
            return value;
        }

        value = this.col.next();
        return value;
    }
}

class WhereEnumerable extends Enumerable {
    constructor(arr, selector) {
        super();
        this.arr = arr;
        this.selector = selector;
    }

    next() {
        var val = this.arr.next();
        if (val.done) {
            return val;
        }

        if (this.selector(val.value)) {
            return val;
        }

        return this.next();
    }
}

class SelectEnumerable extends Enumerable {
    constructor(arr, fn) {
        super();
        this.arr = arr;
        this.fn = fn;
        this._index = 0;
    }

    next() {
        var value = this.arr.next();
        if (value.done) {
            return value;
        }
        return {
            value: this.fn(value.value, this._index++),
            done: false
        };
    }
}

class SelectManyEnumerable extends Enumerable {
    constructor(arr, colSelector, resultSelector) {
        super();
        this.arr = arr;
        this.colSelector = colSelector;
        this.resultSelector = resultSelector || ((_, x) => x);
        this._index = 0;
    }

    next() {
        var value = this.arr.next();
        if (value.done) {
            return value;
        }
        let arr = this.colSelector(value.value, this._index++);
        return {
            value: this.resultSelector(arr, arr[0]),
            done: false
        };
    }
}

class RangeEnumerable extends Enumerable {
    constructor(start, count) {
        super();
        this.count = count;
        this.index = 0;
        this.start = start;
    }

    next() {
        if (this.index === this.count) {
            return {
                done: true
            };
        }

        var value = this.start;
        this.start++;
        this.index++;
        return {
            value: value,
            done: false
        };
    }
}

class TakeEnumerable extends Enumerable {
    constructor(arr, selector) {
        super();
        this.arr = arr;
        this.selector = selector;
        this.index = 0;
    }

    next() {
        var item = this.arr.next();
        if (item.done) {
            return item;
        }

        if (typeof this.selector === 'number') {
            if (this.index < this.selector) {
                this.index++;
                return item;
            } else {
                return {
                    done: true
                };
            }
        } else {
            if (this.selector(item.value, this.index)) {
                this.index++;
                return item;
            } else {
                return {
                    done: true
                };
            }
        }
    }
}

class SkipEnumerable extends Enumerable {
    constructor(arr, selector) {
        super();
        this.arr = arr;
        this.selector = selector;
        this.index = 0;
    }

    next() {
        var item = this.arr.next();
        if (item.done) {
            return item;
        }

        if (typeof this.selector === 'number') {
            if (this.index >= this.selector) {
                this.index++;
                return item;
            } else {
                return this.next();
            }
        } else {
            if (this.selector(item.value, this.index)) {
                return this.next();
            } else {
                return item;
            }
        }
    }
}

class RepeatEnumerable extends Enumerable {
    constructor(item, count) {
        super();
        this.item = item;
        this.count = count;
        this.index = 0;
    }

    next() {
        if (this.index === this.count) {
            return {
                done: true
            };
        }

        var stripped = JSON.stringify(this.item);
        this.index++;
        return {
            value: JSON.parse(stripped),
            done: false
        };
    }
}

// extension methods
Array.prototype.asEnumerable = function() {
    return new Enumerable(this);
};

export default Enumerable;<commit_msg>wrong variable in single<commit_after>'use strict';

var fnTrue = () => true;
var fnSelf = x => x;

let enumerableSymbols = {
    arr: Symbol(),
    index: Symbol()
};

class Enumerable {
    constructor(arr = []) {
        this[enumerableSymbols.arr] = arr;
        this[enumerableSymbols.index] = 0;
    }

    [Symbol.iterator]() {
        return this;
    }

    next() {
        if (this[enumerableSymbols.index] < this[enumerableSymbols.arr].length) {
            return {
                value: this[enumerableSymbols.arr][this[enumerableSymbols.index]++],
                done: false
            };
        }

        return {
            done: true
        };
    }

    first(selector = fnTrue) {
        for (let item of this) {
            if (selector(item)) {
                return item;
            }
        }

        throw new Error('Sequence contains no matching elements');
    }

    firstOrDefault(selector = fnTrue) {
        try {
            return this.first(selector);
        } catch (e) {
            return undefined;
        }
    }

    single(selector = fnTrue) {
        var matched;
        for (let item of this) {
            if (selector(item)) {
                if (matched) {
                    throw Error('Sequence contains more than one matching element');
                } else {
                    matched = item;
                }
            }
        }
        if (matched) {
            return matched;
        }

        throw Error('Sequence contains no matching element');
    }

    singleOrDefault(selector = fnTrue) {
        try {
            return this.single(selector);
        } catch (e) {
            if (e.message === 'Sequence contains no matching elements') {
                return undefined;
            }
            throw e;
        }
    }

    all(fn = fnTrue) {
        for (let x of this) {
            if (!fn(x)) {
                return false;
            }
        }
        return true;
    }

    any(fn = fnTrue) {
        for (let x of this) {
            if (fn(x)) {
                return true;
            }
        }
        return false;
    }

    count(fn = fnTrue) {
        var count = 0;
        for (let i of this) {
            if (fn(i)) {
                count++;
            }
        }
        return count;
    }

    aggregate(seed, fn, selector) {
        var it = this;

        switch (arguments.length) {
            case 1:
                fn = seed;
                seed = it.next().value;
                break;

            case 2:
                if (typeof seed === 'function') {
                    selector = fn;
                    fn = seed;
                    seed = it.next().value;
                }
                break;
        }
        for (let item of it) {
            seed = fn(seed, item);
        }

        if (selector) {
            return selector(seed);
        }
        return seed;
    }

    average(fn = fnSelf) {
        var total = 0;
        var count = 0;

        for (let item of this) {
            total += fn(item);
            count++;
        }

        if (count) {
            return total / count;
        }

        throw Error('Sequence contains no elements');
    }

    concat(col) {
        return new ConcatEnumerable(this, col);
    }

    contains(value, tester = x => x === value) {
        var it = this;

        for (let item of it) {
            if (tester(item, value)) {
                return true;
            }
        }
        return false;
    }

    where(selector) {
        return new WhereEnumerable(this, selector);
    }

    select(fn = fnSelf) {
        return new SelectEnumerable(this, fn);
    }

    map(fn) {
        return this.select(fn);
    }

    selectMany(fn = fnSelf) {
        return new SelectManyEnumerable(this, fn);
    }

    take(count = 0) {
        return new TakeEnumerable(this, count);
    }

    takeWhile(fn = 0) {
        if (fn === 0) {
            return this.take();
        }
        return new TakeEnumerable(this, fn);
    }

    skip(count = 0) {
        return new SkipEnumerable(this, count);
    }

    skipWhile(fn = 0) {
        if (fn === 0) {
            return this.skip();
        }
        return new SkipEnumerable(this, fn);
    }

    toArray() {
        var arr = [];
        for (let i of this) {
            arr.push(i);
        }
        return arr;
    }

    static range(start = 0, end = 0) {
        return new RangeEnumerable(start, end);
    }

    static repeat(item, count = 0) {
        return new RepeatEnumerable(item, count);
    }
}

class ConcatEnumerable extends Enumerable {
    constructor(arr, col) {
        super();
        this.arr = arr;
        this.col = Array.isArray(col) ? col.asEnumerable() : col;
    }

    next() {
        var value = this.arr.next();
        if (!value.done) {
            return value;
        }

        value = this.col.next();
        return value;
    }
}

class WhereEnumerable extends Enumerable {
    constructor(arr, selector) {
        super();
        this.arr = arr;
        this.selector = selector;
    }

    next() {
        var val = this.arr.next();
        if (val.done) {
            return val;
        }

        if (this.selector(val.value)) {
            return val;
        }

        return this.next();
    }
}

class SelectEnumerable extends Enumerable {
    constructor(arr, fn) {
        super();
        this.arr = arr;
        this.fn = fn;
        this._index = 0;
    }

    next() {
        var value = this.arr.next();
        if (value.done) {
            return value;
        }
        return {
            value: this.fn(value.value, this._index++),
            done: false
        };
    }
}

class SelectManyEnumerable extends Enumerable {
    constructor(arr, colSelector, resultSelector) {
        super();
        this.arr = arr;
        this.colSelector = colSelector;
        this.resultSelector = resultSelector || ((_, x) => x);
        this._index = 0;
    }

    next() {
        var value = this.arr.next();
        if (value.done) {
            return value;
        }
        let arr = this.colSelector(value.value, this._index++);
        return {
            value: this.resultSelector(arr, arr[0]),
            done: false
        };
    }
}

class RangeEnumerable extends Enumerable {
    constructor(start, count) {
        super();
        this.count = count;
        this.index = 0;
        this.start = start;
    }

    next() {
        if (this.index === this.count) {
            return {
                done: true
            };
        }

        var value = this.start;
        this.start++;
        this.index++;
        return {
            value: value,
            done: false
        };
    }
}

class TakeEnumerable extends Enumerable {
    constructor(arr, selector) {
        super();
        this.arr = arr;
        this.selector = selector;
        this.index = 0;
    }

    next() {
        var item = this.arr.next();
        if (item.done) {
            return item;
        }

        if (typeof this.selector === 'number') {
            if (this.index < this.selector) {
                this.index++;
                return item;
            } else {
                return {
                    done: true
                };
            }
        } else {
            if (this.selector(item.value, this.index)) {
                this.index++;
                return item;
            } else {
                return {
                    done: true
                };
            }
        }
    }
}

class SkipEnumerable extends Enumerable {
    constructor(arr, selector) {
        super();
        this.arr = arr;
        this.selector = selector;
        this.index = 0;
    }

    next() {
        var item = this.arr.next();
        if (item.done) {
            return item;
        }

        if (typeof this.selector === 'number') {
            if (this.index >= this.selector) {
                this.index++;
                return item;
            } else {
                return this.next();
            }
        } else {
            if (this.selector(item.value, this.index)) {
                return this.next();
            } else {
                return item;
            }
        }
    }
}

class RepeatEnumerable extends Enumerable {
    constructor(item, count) {
        super();
        this.item = item;
        this.count = count;
        this.index = 0;
    }

    next() {
        if (this.index === this.count) {
            return {
                done: true
            };
        }

        var stripped = JSON.stringify(this.item);
        this.index++;
        return {
            value: JSON.parse(stripped),
            done: false
        };
    }
}

// extension methods
Array.prototype.asEnumerable = function() {
    return new Enumerable(this);
};

export default Enumerable;<|endoftext|>


<commit_before>/**
 * jaylist v2
 * jaylist is a wrapper for javascript's built-in hash storage system.
 *
 * Copyright (C) 2011 by Ben Brooks Scholz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @constructor
 * List allows access to elements through a key (string of characters).
 */
var List = function () {
    this.table = {};
};

/**
 * List functions to create and manipulate a hashed list.
 *
 *  get(key)        -- Returns the value associated with key.
 *  add(key, value) -- Insert an object into the list, over-
 *                     writing any value already assigned to the
 *                     key. Returns the value upon successful
 *                     addition.
 *  remove(key)     -- Removes the object from the list. Returns
 *                     false if no value is assigned to the key.
 *                     Upon successful removal, it returns the value
 *                     removed.
 *  keys()          -- Returns an array of the keys in the list.
 *  values()        -- Returns an array of the values in the list
 *                     with each value appearing only once.
 *
 */
List.prototype = {

    /**
     * get: Returns the value attached to the given key or undefined
     * if it isn't found.
     */
    get: function(key) {
        return this.table[key];
    },

    /**
     * add: Inserts an object into the list, assigning it to the given key.
     * It returns the value upon successful addition to the list. If a value
     * is inserted with a key that exists in the list already, the old value
     * is overwritten.
     */
    add: function (key, value) {
        this.table[key] = value;
        return this.table[key];
    },

    /**
     * remove: Removes an object from the list with the given key. It returns
     * false if no object with the given key exists in the list. Otherwise, it
     * returns the value removed.
     */
    remove: function (key) {
        var val;

        // if no value is assigned to the key
        if (this.table[key] === undefined)
            return false;

        // value associated with the key (for return)
        val = this.table[key];
        delete this.table[key];

        return val;
    },

    /**
     * keys: Returns an array of the keys in the List.
     */
    keys: function () {
        var key, keys, table;

        keys = [];
        table = this.table;

        // iterate throught the hash pairs in the table, pushing each key
        // to the keys array.
        for (key in table) {
            // filter inherited properties w/ for...in construct
            if (table.hasOwnProperty(key))
                keys.push(key);
        }

        return keys;
    },

    /**
     * values: Returns an array of the values in the list. Duplicate values
     * are only displayed once.
     */
    values: function () {
        var i, j, contains, keys, values;

        values = [];
        keys = this.keys();

        // iterate through the keys in the list
        for (i = 0; i < keys.length; i = i + 1) {
            // contains is false if the value does not already appear in the
            // values array and true if it is found.
            contains = false;
            for (j = 0; j < values.length; j = j + 1) {
                if (this.get(keys[i]) === values[j]) {
                    contains = true;
                    break;
                }
            }

            // if the value is not already in the values array, push the value
            if (!contains)
                values.push(this.get(keys[i]));
        }

        return values;
    }
};

// functions available to the user
window['List'] = List;
List.prototype['get'] = List.prototype.get;
List.prototype['add'] = List.prototype.add;
List.prototype['remove'] = List.prototype.remove;
List.prototype['keys'] = List.prototype.keys;
List.prototype['values'] = List.prototype.values;
<commit_msg>added len() function<commit_after>/**
 * jaylist v2
 * jaylist is a wrapper for javascript's built-in hash storage system.
 *
 * Copyright (C) 2011 by Ben Brooks Scholz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @constructor
 * List allows access to elements through a key (string of characters).
 */
var List = function () {
    this.table = {};
};

/**
 * List functions to create and manipulate a hashed list.
 *
 *  get(key)        -- Returns the value associated with key.
 *  add(key, value) -- Insert an object into the list, over-
 *                     writing any value already assigned to the
 *                     key. Returns the value upon successful
 *                     addition.
 *  remove(key)     -- Removes the object from the list. Returns
 *                     false if no value is assigned to the key.
 *                     Upon successful removal, it returns the value
 *                     removed.
 *  keys()          -- Returns an array of the keys in the list.
 *  values()        -- Returns an array of the values in the list
 *                     with each value appearing only once.
 *
 */
List.prototype = {

    /**
     * get: Returns the value attached to the given key or undefined
     * if it isn't found.
     */
    get: function(key) {
        return this.table[key];
    },

    /**
     * add: Inserts an object into the list, assigning it to the given key.
     * It returns the value upon successful addition to the list. If a value
     * is inserted with a key that exists in the list already, the old value
     * is overwritten.
     */
    add: function (key, value) {
        this.table[key] = value;
        return this.table[key];
    },

    /**
     * remove: Removes an object from the list with the given key. It returns
     * false if no object with the given key exists in the list. Otherwise, it
     * returns the value removed.
     */
    remove: function (key) {
        var val;

        // if no value is assigned to the key
        if (this.table[key] === undefined)
            return false;

        // value associated with the key (for return)
        val = this.table[key];
        delete this.table[key];

        return val;
    },

    /**
     * keys: Returns an array of the keys in the List.
     */
    keys: function () {
        var key, keys, table;

        keys = [];
        table = this.table;

        // iterate throught the hash pairs in the table, pushing each key
        // to the keys array.
        for (key in table) {
            // filter inherited properties w/ for...in construct
            if (table.hasOwnProperty(key))
                keys.push(key);
        }

        return keys;
    },

    /**
     * values: Returns an array of the values in the list. Duplicate values
     * are only displayed once.
     */
    values: function () {
        var i, j, contains, keys, values;

        values = [];
        keys = this.keys();

        // iterate through the keys in the list
        for (i = 0; i < keys.length; i = i + 1) {
            // contains is false if the value does not already appear in the
            // values array and true if it is found.
            contains = false;
            for (j = 0; j < values.length; j = j + 1) {
                if (this.get(keys[i]) === values[j]) {
                    contains = true;
                    break;
                }
            }

            // if the value is not already in the values array, push the value
            if (!contains)
                values.push(this.get(keys[i]));
        }

        return values;
    },

    /**
     * len: Returns the number of items in the list. Returns zero if empty.
     */
    len: function () {
        var len, entry;
        len = 0;
        for (entry in this.table)
            len = len + 1;

        return len;
    }
};

// functions available to the user
window['List'] = List;
List.prototype['get'] = List.prototype.get;
List.prototype['add'] = List.prototype.add;
List.prototype['remove'] = List.prototype.remove;
List.prototype['keys'] = List.prototype.keys;
List.prototype['values'] = List.prototype.values;
List.prototype['len'] = List.prototype.len;
<|endoftext|>


<commit_before>(function(DOM, BASE_CLASS, VK_SPACE, VK_TAB, VK_ENTER, VK_ESCAPE, VK_BACKSPACE, VK_DELETE, DateUtils) {
    "use strict";

    var __ = DOM.__,
        ampm = (pos, neg) => DOM.get("lang") === "en-US" ? pos : neg,
        formatISODate = (value) => value.toISOString().split("T")[0],
        PICKER_TEMPLATE = DOM.create("div.{0}>p.{0}-header>a[{1}]*2+span[{2} {1}].{0}-caption^table[{2}].{0}-days>thead>(tr>th[{1}]*7)^(tbody.{0}-body*2>tr*6>td*7)", [`${BASE_CLASS}-calendar`, "unselectable=on", "aria-hidden=true"]),
        LABEL_TEMPLATE = DOM.create("span[aria-hidden=true].{0}-value", [BASE_CLASS]),
        readDateRange = (el) => ["min", "max"].map((x) => new Date(el.get(x) || "")),
        pad = (num, maxlen) => ((maxlen === 2 ? "0" : "00") + num).slice(-maxlen);

    // Feature detect browsers that already support date inputs
    var hasNativeSupport = DOM.create("input[type=date]")[0].type === "date";

    // need to skip mobile/tablet browsers
    DOM.extend("input[type=date]", !( window.orientation || hasNativeSupport ), {
        constructor() {
            var calendar = PICKER_TEMPLATE.clone(true),
                label = LABEL_TEMPLATE.clone(true),
                color = this.css("color");

            this
                // hide original input text
                // IE8 doesn't suport color:transparent - use background-color instead
                .css("color", document.addEventListener ? "transparent" : this.css("background-color"))
                // handle arrow keys, esc etc.
                .on("keydown", ["which", "shiftKey"], this._keydownCalendar.bind(this, calendar))
                // sync picker visibility on focus/blur
                .on(["focus", "click"], this._focusCalendar.bind(this, calendar))
                .on("blur", this._blurCalendar.bind(this, calendar))
                .on("change", this._formatValue.bind(this, label))
                .before(calendar.hide(), label);

            label
                .on("click", () => { this.fire("focus") })
                // copy input CSS to adjust visible text position
                .css(this.css(["width", "font", "padding-left", "padding-right", "text-align", "border-width", "box-sizing"]));

            var calenderDays = calendar.findAll(`.${BASE_CLASS}-calendar-body`),
                calendarCaption = calendar.find(`.${BASE_CLASS}-calendar-caption`),
                changeValue = this._changeValue.bind(this, calendarCaption, calenderDays, calendar);

            calenderDays[1].hide().remove();

            this.closest("form").on("reset", this._resetForm.bind(this));
            this.watch("value", changeValue);
            // trigger watchers to build the calendar
            changeValue(this.value());

            calendar.on("mousedown", ["target"], this._clickCalendar.bind(this, calendar));
            // FIXME: get rid of DOM.requestFrame which is required to get right offset
            DOM.requestFrame(() => {
                var offset = this.offset();
                var labelOffset = label.offset();

                label.css({
                    "color": color,
                    "line-height": offset.height + "px",
                    "margin-left": offset.left - labelOffset.left,
                    "margin-top": offset.top - labelOffset.top
                });

                calendar
                    .css({
                        "margin-left": offset.left - labelOffset.left,
                        "margin-top": offset.bottom - labelOffset.top,
                        "z-index": 1 + (this.css("z-index") | 0)
                    });

                // FIXME
                // move calendar to the top when passing cross browser window bounds
                // if (DOM.get("clientHeight") < offset.bottom + calOffset.height) {
                //     calendar.css("margin-top", calOffset.top - offset.bottom - calOffset.height);
                // }

                // display calendar for autofocused elements
                if (this.matches(":focus")) this.fire("focus");
            });
        },
        _changeValue(caption, calenderDays, calendar, value, prevValue) {
            var year, month, date, iterDate;

            value = new Date(value);

            if (!value.getTime()) {
                value = new Date();
            }

            month = value.getUTCMonth();
            date = value.getUTCDate();
            year = value.getUTCFullYear();
            // update calendar caption
            caption.set(__(DateUtils.MONTHS[month]).toHTMLString() + " " + year);
            // update calendar content
            iterDate = new Date(Date.UTC(year, month, 0));
            // move to beginning of current month week
            iterDate.setUTCDate(iterDate.getUTCDate() - iterDate.getUTCDay() - ampm(1, 0));

            prevValue = new Date(prevValue);

            var delta = value.getUTCMonth() - prevValue.getUTCMonth() + 100 * (value.getUTCFullYear() - prevValue.getUTCFullYear());
            var currenDays = calenderDays[calendar.contains(calenderDays[0]) ? 0 : 1];
            var targetDays = delta ? calenderDays[calenderDays[0] === currenDays ? 1 : 0] : currenDays;
            var range = readDateRange(this);

            // update days
            targetDays.findAll("td").forEach((day) => {
                iterDate.setUTCDate(iterDate.getUTCDate() + 1);

                var mDiff = month - iterDate.getUTCMonth(),
                    className = `${BASE_CLASS}-calendar-`;

                if (year !== iterDate.getUTCFullYear()) mDiff *= -1;

                if (iterDate < range[0] || iterDate > range[1]) {
                    className += "out";
                } else if (mDiff > 0) {
                    className += "past";
                } else if (mDiff < 0) {
                    className += "future";
                } else if (date === iterDate.getUTCDate()) {
                    className += "today";
                } else {
                    className = "";
                }

                day.set({
                    _ts: iterDate.getTime(),
                    className: className,
                    textContent: iterDate.getUTCDate()
                });
            });

            if (delta) {
                currenDays[delta > 0 ? "after" : "before"](targetDays);
                currenDays.hide(() => { currenDays.remove() });
                targetDays.show();
            }

            // trigger event manually to notify about changes
            this.fire("change");
        },
        _formatValue(label) {
            var value = new Date(this.get()),
                formattedValue = "";

            if (value.getTime()) {
                var formatString = this.get("data-format");
                // use "E, dd MMM yyyy" as default value
                if (!formatString) formatString = "E, dd MMM yyyy";

                var day = value.getUTCDay();
                var date = value.getUTCDate();
                var month = value.getUTCMonth();
                var year = value.getUTCFullYear();

                formatString = formatString
                        .replace(/'([^']+)'/g, "->$1<-")
                        .replace(/\w+/g, "{$&}")
                        .replace(/->{(.*?)}<-/g, (_, group) => group.replace(/}|{/g, ""));

                formattedValue = DOM.format(formatString, {
                    E: __(DateUtils.DAYS[day].slice(0, 2)).toHTMLString(),
                    EE: __(DateUtils.DAYS[day]).toHTMLString(),
                    d: date,
                    dd: pad(date, 2),
                    D: DateUtils.getDayInYear(value),
                    DD: pad(DateUtils.getDayInYear(value), 3),
                    w: DateUtils.getWeekInYear(value),
                    ww: pad(DateUtils.getWeekInYear(value), 2),
                    W: DateUtils.getWeekInMonth(value),
                    M: month + 1,
                    MM: pad(month + 1, 2),
                    MMM: __(DateUtils.MONTHS[month].substr(0, 3) + ".").toHTMLString(),
                    MMMM: __(DateUtils.MONTHS[month]).toHTMLString(),
                    y: year % 100,
                    yy: pad(year % 100, 2),
                    yyyy: year,
                    u: day || 7,
                    F: DateUtils.getWeekCountInMonth(value)
                });
            }

            // display formatted date value instead of real one
            label.value(formattedValue);
        },
        _clickCalendar(calendar, target) {
            var targetDate;

            if (target.matches("a")) {
                targetDate = new Date(this.get());

                if (!targetDate.getTime()) targetDate = new Date();

                targetDate.setUTCMonth(targetDate.getUTCMonth() + (target.next("a")[0] ? -1 : 1));
            } else if (target.matches("td")) {
                targetDate = target.get("_ts");

                if (targetDate) {
                    targetDate = new Date(targetDate);
                    calendar.hide();
                }
            }

            if (targetDate != null) {
                var range = readDateRange(this);

                if (targetDate < range[0]) {
                    targetDate = range[0];
                } else if (targetDate > range[1]) {
                    targetDate = range[1];
                }

                this.value(formatISODate(targetDate));
            }
            // prevent input from loosing focus
            return false;
        },
        _keydownCalendar(calendar, which, shiftKey) {
            var delta, currentDate;

            // ENTER key should submit form if calendar is hidden
            if (calendar.matches(":hidden") && which === VK_ENTER) return true;

            if (which === VK_SPACE) {
                calendar.toggle(); // SPACE key toggles calendar visibility
            } else if (which === VK_ESCAPE || which === VK_TAB || which === VK_ENTER) {
                calendar.hide(); // ESC, TAB or ENTER keys hide calendar
            } else if (which === VK_BACKSPACE || which === VK_DELETE) {
                this.empty(); // BACKSPACE, DELETE clear value
            } else {
                currentDate = new Date(this.get());

                if (!currentDate.getTime()) currentDate = new Date();

                if (which === 74 || which === 40) { delta = 7; }
                else if (which === 75 || which === 38) { delta = -7; }
                else if (which === 76 || which === 39) { delta = 1; }
                else if (which === 72 || which === 37) { delta = -1; }

                if (delta) {
                    if (shiftKey && (which === 40 || which === 38)) {
                        currentDate.setUTCFullYear(currentDate.getUTCFullYear() + (delta > 0 ? 1 : -1));
                    } else if (shiftKey && (which === 37 || which === 39)) {
                        currentDate.setUTCMonth(currentDate.getUTCMonth() + (delta > 0 ? 1 : -1));
                    } else {
                        currentDate.setUTCDate(currentDate.getUTCDate() + delta);
                    }

                    var range = readDateRange(this);

                    if (!(currentDate < range[0] || currentDate > range[1])) {
                        this.value(formatISODate(currentDate));
                    }
                }
            }
            // prevent default action except if it was TAB so
            // do not allow to change the value manually
            return which === VK_TAB;
        },
        _blurCalendar(calendar) {
            calendar.hide();
        },
        _focusCalendar(calendar) {
            // update calendar weekday captions
            calendar.findAll("th").forEach((el, index) => {
                el.l10n(DateUtils.DAYS[ampm(index, ++index % 7)].slice(0, 2));
            });

            calendar.show();

            // use the trick below to reset text selection on focus
            setTimeout(() => {
                var node = this[0];

                if ("selectionStart" in node) {
                    node.selectionStart = 0;
                    node.selectionEnd = 0;
                } else {
                    var inputRange = node.createTextRange();

                    inputRange.moveStart("character", 0);
                    inputRange.collapse();
                    inputRange.moveEnd("character", 0);
                    inputRange.select();
                }
            }, 0);
        },
        _resetForm() {
            this.value(this.get("defaultValue"));
        }
    });

    // compact moths in english don't have the dot suffix
    DOM.importStrings("en", DateUtils.MONTHS.reduce((memo, month) => {
        var shortMonth = month.slice(0, 3);

        memo[shortMonth + "."] = shortMonth;

        return memo;
    }, {}));
}(window.DOM, "btr-dateinput", 32, 9, 13, 27, 8, 46, {
    DAYS: "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),
    MONTHS: "January February March April May June July August September October November December".split(" "),
    getWeekInYear(d) {
        d = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
        // set to nearest thursday: current date + 4 - current day number
        // make sunday's day number 7
        d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
        var yearStart = Date.UTC(d.getUTCFullYear(), 0, 1);
        // calculate full weeks to nearest thursday
        return Math.ceil((1 + (d - yearStart) / 86400000) / 7);
    },
    getWeekInMonth(d) {
        var firstWeekday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1)).getUTCDay();
        var offsetDate = d.getUTCDate() + firstWeekday - 1;
        return 1 + Math.floor(offsetDate / 7);
    },
    getWeekCountInMonth(d) {
        return Math.ceil(d.getUTCDate() / 7);
    },
    getDayInYear(d) {
        var beginOfYear = Date.UTC(d.getUTCFullYear(), 0, 1);
        var millisBetween = d.getTime() - beginOfYear;
        return Math.floor(1 + millisBetween / 86400000);
    }
}));
<commit_msg>fix for #33: implement support for data-native<commit_after>(function(DOM, BASE_CLASS, VK_SPACE, VK_TAB, VK_ENTER, VK_ESCAPE, VK_BACKSPACE, VK_DELETE, DateUtils, testDateInput) {
    "use strict";

    var __ = DOM.__,
        ampm = (pos, neg) => DOM.get("lang") === "en-US" ? pos : neg,
        formatISODate = (value) => value.toISOString().split("T")[0],
        PICKER_TEMPLATE = DOM.create("div.{0}>p.{0}-header>a[{1}]*2+span[{2} {1}].{0}-caption^table[{2}].{0}-days>thead>(tr>th[{1}]*7)^(tbody.{0}-body*2>tr*6>td*7)", [`${BASE_CLASS}-calendar`, "unselectable=on", "aria-hidden=true"]),
        LABEL_TEMPLATE = DOM.create("span[aria-hidden=true].{0}-value", [BASE_CLASS]),
        readDateRange = (el) => ["min", "max"].map((x) => new Date(el.get(x) || "")),
        pad = (num, maxlen) => ((maxlen === 2 ? "0" : "00") + num).slice(-maxlen);

    // need to skip mobile/tablet browsers
    DOM.extend("input[type=date]", testDateInput, {
        constructor() {
            var calendar = PICKER_TEMPLATE.clone(true),
                label = LABEL_TEMPLATE.clone(true),
                color = this.css("color");

            this
                // hide original input text
                // IE8 doesn't suport color:transparent - use background-color instead
                .css("color", document.addEventListener ? "transparent" : this.css("background-color"))
                // handle arrow keys, esc etc.
                .on("keydown", ["which", "shiftKey"], this._keydownCalendar.bind(this, calendar))
                // sync picker visibility on focus/blur
                .on(["focus", "click"], this._focusCalendar.bind(this, calendar))
                .on("blur", this._blurCalendar.bind(this, calendar))
                .on("change", this._formatValue.bind(this, label))
                .before(calendar.hide(), label);

            label
                .on("click", () => { this.fire("focus") })
                // copy input CSS to adjust visible text position
                .css(this.css(["width", "font", "padding-left", "padding-right", "text-align", "border-width", "box-sizing"]));

            var calenderDays = calendar.findAll(`.${BASE_CLASS}-calendar-body`),
                calendarCaption = calendar.find(`.${BASE_CLASS}-calendar-caption`),
                changeValue = this._changeValue.bind(this, calendarCaption, calenderDays, calendar);

            calenderDays[1].hide().remove();

            this.closest("form").on("reset", this._resetForm.bind(this));
            this.watch("value", changeValue);
            // trigger watchers to build the calendar
            changeValue(this.value());

            calendar.on("mousedown", ["target"], this._clickCalendar.bind(this, calendar));
            // FIXME: get rid of DOM.requestFrame which is required to get right offset
            DOM.requestFrame(() => {
                var offset = this.offset();
                var labelOffset = label.offset();

                label.css({
                    "color": color,
                    "line-height": offset.height + "px",
                    "margin-left": offset.left - labelOffset.left,
                    "margin-top": offset.top - labelOffset.top
                });

                calendar
                    .css({
                        "margin-left": offset.left - labelOffset.left,
                        "margin-top": offset.bottom - labelOffset.top,
                        "z-index": 1 + (this.css("z-index") | 0)
                    });

                // FIXME
                // move calendar to the top when passing cross browser window bounds
                // if (DOM.get("clientHeight") < offset.bottom + calOffset.height) {
                //     calendar.css("margin-top", calOffset.top - offset.bottom - calOffset.height);
                // }

                // display calendar for autofocused elements
                if (this.matches(":focus")) this.fire("focus");
            });
        },
        _changeValue(caption, calenderDays, calendar, value, prevValue) {
            var year, month, date, iterDate;

            value = new Date(value);

            if (!value.getTime()) {
                value = new Date();
            }

            month = value.getUTCMonth();
            date = value.getUTCDate();
            year = value.getUTCFullYear();
            // update calendar caption
            caption.set(__(DateUtils.MONTHS[month]).toHTMLString() + " " + year);
            // update calendar content
            iterDate = new Date(Date.UTC(year, month, 0));
            // move to beginning of current month week
            iterDate.setUTCDate(iterDate.getUTCDate() - iterDate.getUTCDay() - ampm(1, 0));

            prevValue = new Date(prevValue);

            var delta = value.getUTCMonth() - prevValue.getUTCMonth() + 100 * (value.getUTCFullYear() - prevValue.getUTCFullYear());
            var currenDays = calenderDays[calendar.contains(calenderDays[0]) ? 0 : 1];
            var targetDays = delta ? calenderDays[calenderDays[0] === currenDays ? 1 : 0] : currenDays;
            var range = readDateRange(this);

            // update days
            targetDays.findAll("td").forEach((day) => {
                iterDate.setUTCDate(iterDate.getUTCDate() + 1);

                var mDiff = month - iterDate.getUTCMonth(),
                    className = `${BASE_CLASS}-calendar-`;

                if (year !== iterDate.getUTCFullYear()) mDiff *= -1;

                if (iterDate < range[0] || iterDate > range[1]) {
                    className += "out";
                } else if (mDiff > 0) {
                    className += "past";
                } else if (mDiff < 0) {
                    className += "future";
                } else if (date === iterDate.getUTCDate()) {
                    className += "today";
                } else {
                    className = "";
                }

                day.set({
                    _ts: iterDate.getTime(),
                    className: className,
                    textContent: iterDate.getUTCDate()
                });
            });

            if (delta) {
                currenDays[delta > 0 ? "after" : "before"](targetDays);
                currenDays.hide(() => { currenDays.remove() });
                targetDays.show();
            }

            // trigger event manually to notify about changes
            this.fire("change");
        },
        _formatValue(label) {
            var value = new Date(this.get()),
                formattedValue = "";

            if (value.getTime()) {
                var formatString = this.get("data-format");
                // use "E, dd MMM yyyy" as default value
                if (!formatString) formatString = "E, dd MMM yyyy";

                var day = value.getUTCDay();
                var date = value.getUTCDate();
                var month = value.getUTCMonth();
                var year = value.getUTCFullYear();

                formatString = formatString
                        .replace(/'([^']+)'/g, "->$1<-")
                        .replace(/\w+/g, "{$&}")
                        .replace(/->{(.*?)}<-/g, (_, group) => group.replace(/}|{/g, ""));

                formattedValue = DOM.format(formatString, {
                    E: __(DateUtils.DAYS[day].slice(0, 2)).toHTMLString(),
                    EE: __(DateUtils.DAYS[day]).toHTMLString(),
                    d: date,
                    dd: pad(date, 2),
                    D: DateUtils.getDayInYear(value),
                    DD: pad(DateUtils.getDayInYear(value), 3),
                    w: DateUtils.getWeekInYear(value),
                    ww: pad(DateUtils.getWeekInYear(value), 2),
                    W: DateUtils.getWeekInMonth(value),
                    M: month + 1,
                    MM: pad(month + 1, 2),
                    MMM: __(DateUtils.MONTHS[month].substr(0, 3) + ".").toHTMLString(),
                    MMMM: __(DateUtils.MONTHS[month]).toHTMLString(),
                    y: year % 100,
                    yy: pad(year % 100, 2),
                    yyyy: year,
                    u: day || 7,
                    F: DateUtils.getWeekCountInMonth(value)
                });
            }

            // display formatted date value instead of real one
            label.value(formattedValue);
        },
        _clickCalendar(calendar, target) {
            var targetDate;

            if (target.matches("a")) {
                targetDate = new Date(this.get());

                if (!targetDate.getTime()) targetDate = new Date();

                targetDate.setUTCMonth(targetDate.getUTCMonth() + (target.next("a")[0] ? -1 : 1));
            } else if (target.matches("td")) {
                targetDate = target.get("_ts");

                if (targetDate) {
                    targetDate = new Date(targetDate);
                    calendar.hide();
                }
            }

            if (targetDate != null) {
                var range = readDateRange(this);

                if (targetDate < range[0]) {
                    targetDate = range[0];
                } else if (targetDate > range[1]) {
                    targetDate = range[1];
                }

                this.value(formatISODate(targetDate));
            }
            // prevent input from loosing focus
            return false;
        },
        _keydownCalendar(calendar, which, shiftKey) {
            var delta, currentDate;

            // ENTER key should submit form if calendar is hidden
            if (calendar.matches(":hidden") && which === VK_ENTER) return true;

            if (which === VK_SPACE) {
                calendar.toggle(); // SPACE key toggles calendar visibility
            } else if (which === VK_ESCAPE || which === VK_TAB || which === VK_ENTER) {
                calendar.hide(); // ESC, TAB or ENTER keys hide calendar
            } else if (which === VK_BACKSPACE || which === VK_DELETE) {
                this.empty(); // BACKSPACE, DELETE clear value
            } else {
                currentDate = new Date(this.get());

                if (!currentDate.getTime()) currentDate = new Date();

                if (which === 74 || which === 40) { delta = 7; }
                else if (which === 75 || which === 38) { delta = -7; }
                else if (which === 76 || which === 39) { delta = 1; }
                else if (which === 72 || which === 37) { delta = -1; }

                if (delta) {
                    if (shiftKey && (which === 40 || which === 38)) {
                        currentDate.setUTCFullYear(currentDate.getUTCFullYear() + (delta > 0 ? 1 : -1));
                    } else if (shiftKey && (which === 37 || which === 39)) {
                        currentDate.setUTCMonth(currentDate.getUTCMonth() + (delta > 0 ? 1 : -1));
                    } else {
                        currentDate.setUTCDate(currentDate.getUTCDate() + delta);
                    }

                    var range = readDateRange(this);

                    if (!(currentDate < range[0] || currentDate > range[1])) {
                        this.value(formatISODate(currentDate));
                    }
                }
            }
            // prevent default action except if it was TAB so
            // do not allow to change the value manually
            return which === VK_TAB;
        },
        _blurCalendar(calendar) {
            calendar.hide();
        },
        _focusCalendar(calendar) {
            // update calendar weekday captions
            calendar.findAll("th").forEach((el, index) => {
                el.l10n(DateUtils.DAYS[ampm(index, ++index % 7)].slice(0, 2));
            });

            calendar.show();

            // use the trick below to reset text selection on focus
            setTimeout(() => {
                var node = this[0];

                if ("selectionStart" in node) {
                    node.selectionStart = 0;
                    node.selectionEnd = 0;
                } else {
                    var inputRange = node.createTextRange();

                    inputRange.moveStart("character", 0);
                    inputRange.collapse();
                    inputRange.moveEnd("character", 0);
                    inputRange.select();
                }
            }, 0);
        },
        _resetForm() {
            this.value(this.get("defaultValue"));
        }
    });

    // compact moths in english don't have the dot suffix
    DOM.importStrings("en", DateUtils.MONTHS.reduce((memo, month) => {
        var shortMonth = month.slice(0, 3);

        memo[shortMonth + "."] = shortMonth;

        return memo;
    }, {}));
}(window.DOM, "btr-dateinput", 32, 9, 13, 27, 8, 46, {
    DAYS: "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),
    MONTHS: "January February March April May June July August September October November December".split(" "),
    getWeekInYear(d) {
        d = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
        // set to nearest thursday: current date + 4 - current day number
        // make sunday's day number 7
        d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
        var yearStart = Date.UTC(d.getUTCFullYear(), 0, 1);
        // calculate full weeks to nearest thursday
        return Math.ceil((1 + (d - yearStart) / 86400000) / 7);
    },
    getWeekInMonth(d) {
        var firstWeekday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1)).getUTCDay();
        var offsetDate = d.getUTCDate() + firstWeekday - 1;
        return 1 + Math.floor(offsetDate / 7);
    },
    getWeekCountInMonth(d) {
        return Math.ceil(d.getUTCDate() / 7);
    },
    getDayInYear(d) {
        var beginOfYear = Date.UTC(d.getUTCFullYear(), 0, 1);
        var millisBetween = d.getTime() - beginOfYear;
        return Math.floor(1 + millisBetween / 86400000);
    }
}, (el) => {
    var nativeValue = el.get("_native"),
        deviceType = "orientation" in window ? "mobile" : "desktop";

    if (!nativeValue || nativeValue === deviceType) {
        // by default test if the type property is "date"
        // to determine if the device supports native control
        return el[0].type !== "date";
    } else {
        // remove native control
        el.set("type", "text");
        // force applying the polyfill
        return true;
    }
}));
<|endoftext|>


<commit_before>'use strict'
// var injectTapEventPlugin = require("react-tap-event-plugin");
// injectTapEventPlugin();
import 'normalize.css'

// require('assets/fonts.css');
// require('./styles/main.less');

import React from 'react'
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { Provider, connect } from 'react-redux'
import thunk from 'redux-thunk'

import * as reducers from './reducers'
import App from './components/App'

function select (state) {
  return state.data
}

let ConnectedApp = connect(select)(App)
let render = (store, rootElement) => {
  React.render(
    <Provider store={store}>
      {() => <ConnectedApp />}
    </Provider>,
    rootElement
  )
}

let enhanceStore = applyMiddleware(thunk)
const reducer = combineReducers(reducers)
const createAppStore = enhanceStore(createStore)

render(createAppStore(reducer), document.getElementById('content'))
<commit_msg>don't keep updating when there are no more updates to be made<commit_after>'use strict'
// var injectTapEventPlugin = require("react-tap-event-plugin");
// injectTapEventPlugin();
import 'normalize.css'

// require('assets/fonts.css');
// require('./styles/main.less');

import React from 'react'
import { createStore, applyMiddleware, combineReducers } from 'redux'
import { Provider, connect } from 'react-redux'
import thunk from 'redux-thunk'

import * as reducers from './reducers'
import App from './components/App'

function select (state) {
  return state.data
}

let ConnectedApp = connect(select)(App)
let render = (store, rootElement) => {
  React.render(
    <Provider store={store}>
      {() => <ConnectedApp />}
    </Provider>,
    rootElement
  )
}

import _ from 'lodash'
let prevGrid
let stopUpdates = false
let enhanceStore = applyMiddleware(
  thunk,
  ({getState}) => next => action => {
    if (action.type === 'STEP' && !stopUpdates) {
      next(action)
      let grid = getState().data.grid
      if (_.eq(grid, prevGrid)) {
        stopUpdates = true
      } else {
        prevGrid = grid
      }
    }
  }
)
const reducer = combineReducers(reducers)
const createAppStore = enhanceStore(createStore)

render(createAppStore(reducer), document.getElementById('content'))
<|endoftext|>


<commit_before>'use strict'

const TheadLayout=require('./thead-layout')
const data=require('./data')

$(function(){
	$('.crnx-ode-properties').each(function(){
		const $container=$(this)
		const dag={}, idag={}
		const selectedNodes={}
		for (let id in data) {
			if (data[id].importance<=1) {
				selectedNodes[id]=true
			}
			dag[id]=data[id].parents
			idag[id]={}
		}
		for (let id in data) {
			for (let pid in dag[id]) {
				idag[pid][id]=true
			}
		}
		let theadLayout=new TheadLayout(dag,selectedNodes)
		const breadthWalk=(graph,id)=>{
			const result=[]
			const visited={}
			const queue=[id]
			while (queue.length>0) {
				id=queue.shift()
				if (visited[id]) {
					continue
				}
				visited[id]=true
				if (!selectedNodes[id]) {
					result.push(id)
				}
				queue.push(...Object.keys(graph[id]).sort())
			}
			return result
		}
		const writeButton=(text,tip)=>{
			const $button=$(`<button type='button'><span>${text}</span></button>`)
			if (tip!==undefined) {
				$button.attr('title',tip)
			}
			return $button
		}
		const writeData=(name,id)=>{
			const output=[]

			// mark ancestors of visible ancestors as visited
			const visited={}
			const markAncestors=id=>{
				if (visited[id]) return
				visited[id]=true
				Object.keys(data[id].parents).forEach(pid=>{
					markAncestors(pid)
				})
			}
			const reachAncestors=id=>{
				if (visited[id]) return
				Object.keys(data[id].parents).forEach(pid=>{
					if (selectedNodes[pid]) {
						markAncestors(pid)
					} else {
						reachAncestors(pid)
					}
				})
			}
			reachAncestors(id)

			const overrides={}
			const raiseOverride=iid=>{
				if (overrides[iid]===undefined) {
					overrides[iid]=0
				}
				overrides[iid]++
			}
			const lowerOverride=iid=>{
				overrides[iid]--
			}
			const rec=cid=>{
				visited[cid]=true
				const forOverrides=fn=>{
					if (data[cid][name]) {
						data[cid][name].forEach(item=>{
							item.forEach(section=>{
								const type=section[0]
								if (section[0]=='override') {
									const iids=section[1]
									iids.forEach(iid=>{
										fn(iid)
									})
								}
							})
						})
					}
				}
				// recursion on nodes that are not selected for display
				forOverrides(raiseOverride)
				Object.keys(data[cid].parents).sort().forEach(pid=>{
					if (!visited[pid]) {
						rec(pid)
					}
				})
				forOverrides(lowerOverride)
				// output of things that are not overridden by children
				if (data[cid][name]) {
					data[cid][name].forEach(item=>{
						let skip=false
						item.forEach(section=>{
							const type=section[0]
							if (type=='id') {
								const iid=section[1]
								if (overrides[iid]) {
									skip=true
								}
							}
						})
						if (skip) return
						const outItem=[]
						item.forEach(section=>{
							const type=section[0]
							if (type=='id' || type=='override') return
							if (type=='form') {
								if (cid!=id) {
									outItem.push(['note',[
										"when equation is written as "+data[cid].htmlName+":",
										"\\["+data[cid].equation+"\\]",
									]])
								}
							} else {
								outItem.push(section)
							}
						})
						output.push(outItem)
					})
				}
			}
			rec(id)
			if (output.length==0) return $()
			return $("
").append(output.map(item=>{
				return $("
").append(item.map(section=>{
					const type=section[0], contents=section[1]
					const $section=$(`<div class='${type}'>`).append(contents.map(line=>{
						if (type=='title') {
							return $(`<div><em>${line}</em>:</div>`)
						} else {
							return $(`<div>${line}</div>`)
						}
					}))
					if (type=='detail') {
						const $b1=writeButton("Open","Expand details")
						const $b2=writeButton("Open","Expand details")
						$section.prepend($b1).append($b2)
						const $bs=$b1.add($b2)
						$bs.click(function(){
							if (!$section.hasClass('open')) {
								$section.addClass('open')
								$bs.html("Close").attr('title',"Collapse details")
							} else {
								$section.removeClass('open')
								$bs.html("Open").attr('title',"Expand details")
							}
						})
					}
					return $section
				}))
			}))
		}
		const writeTable=()=>{
			let $attachedMenu, $attachedToButton, attachedDirection, attachTimeoutId
			const deleteNode=id=>{
				delete selectedNodes[id]
				theadLayout=new TheadLayout(dag,selectedNodes)
				writeTable()
			}
			const addNode=id=>{
				selectedNodes[id]=true
				theadLayout=new TheadLayout(dag,selectedNodes)
				writeTable()
			}
			const setCellClasses=($cell,cell)=>{
				;['b','t','bt','rl','rt','bl'].forEach(dir=>{
					if (cell[dir]) {
						$cell.addClass(dir)
					}
				})
			}
			const writeTheadButton=($cell,text,tip,dir,nodes)=>{
				const keyCodeEnter=13
				const keyCodeSpace=32
				const keyCodeUp=38
				const keyCodeDown=40
				const $button=writeButton(text,tip).addClass(dir).click(function(){
					const removeAttachedMenu=()=>{
						$attachedToButton.removeClass('attached-t attached-b')
						$attachedMenu.remove()
						clearTimeout(attachTimeoutId)
						$attachedToButton=undefined
						$attachedMenu=undefined
						attachedDirection=undefined
						attachTimeoutId=undefined
					}
					const startAttachingMenu=()=>{
						$attachedToButton=$button
						$attachedMenu=$("
").append(nodes.map(
							id=>$("
"+data[id].name+"
").click(function(){
								addNode(id)
							}).keydown(function(ev){
								if (!attachedDirection) return // not yet decided on attach direction (this can't happen)
								if (ev.keyCode==keyCodeUp) {
									const $toFocus=$(this).prev()
									if ($toFocus.length) {
										$toFocus.focus()
									} else if (attachedDirection=='b') {
										$button.focus()
									}
								} else if (ev.keyCode==keyCodeDown) {
									const $toFocus=$(this).next()
									if ($toFocus.length) {
										$toFocus.focus()
									} else if (attachedDirection=='t') {
										$button.focus()
									}
								} else if (ev.keyCode==keyCodeEnter || ev.keyCode==keyCodeSpace) {
									addNode(id)
								}
							})
						))
						$cell.append($attachedMenu)
						attachTimeoutId=setTimeout(()=>{ // can calculate height only after it's displayed
							const mh=$attachedMenu.outerHeight()
							const mo=$attachedMenu.offset()
							const bh=$button.outerHeight()
							const bo=$button.offset()
							let t=bo.top-mh+1
							if (dir!='t' || t<0) { // want below or doesn't fit to screen above the button
								t=bo.top+bh-1
								attachedDirection='b'
							} else {
								$attachedMenu.insertBefore($button)
								attachedDirection='t'
							}
							$attachedMenu.offset({
								top: t,
								left: mo.left,
							})
							$attachedMenu.addClass('attached')
							$button.addClass('attached-'+attachedDirection)
						},0)
					}
					if (!$button.is($attachedToButton)) {
						if ($attachedToButton) {
							removeAttachedMenu() // close menu opened elsewhere
						}
						startAttachingMenu()
					} else {
						removeAttachedMenu() // close menu here
					}
				}).keydown(function(ev){
					if ($button.is($attachedToButton)) {
						if (ev.keyCode==keyCodeUp && attachedDirection=='t') {
							$attachedMenu.children().last().focus()
						} else if (ev.keyCode==keyCodeDown && attachedDirection=='b') {
							$attachedMenu.children().first().focus()
						}
					}
				})
				return $button
			}
			const writeTheadCell=cell=>{
				const $cell=$("")
				setCellClasses($cell,cell)
				if (cell.node!==undefined) {
					$cell.append(data[cell.node].htmlName!==undefined
						? data[cell.node].htmlName
						: data[cell.node].name
					)
					const parents=breadthWalk(dag,cell.node).reverse()
					let $parents
					if (parents.length>0) {
						$cell.append(
							" ",
							writeTheadButton($cell,"Add parent","Add one of supertypes of this equation type",'t',parents)
						)
					}
					const children=breadthWalk(idag,cell.node)
					if (children.length>0) {
						$cell.append(
							" ",
							writeTheadButton($cell,"Add child","Add one of subtypes of this equation type",'b',children)
						)
					}
				}
				return $cell
			}
			const writeThead=()=>{
				const $thead=$("
")
				for (let i=0;i<theadLayout.nodeLayers.length;i++) {
					$thead.append(
						$("
").append(
							theadLayout.nodeLayers[i].map(writeTheadCell)
						)
					)
					if (i<theadLayout.arcLayers.length) {
						$thead.append(
							theadLayout.arcLayers[i].map(row=>$("
").append(
								row.map(cell=>{
									const $cell=$("")
									setCellClasses($cell,cell)
									return $cell
								})
							))
						)
					}
				}
				return $thead
			}
			const writeTfoot=()=>{
				if (theadLayout.columns.length<=1) {
					return $()
				}
				return $("
").append(
					$("
").append(
						theadLayout.columns.map(id=>$("").append(
							writeButton("Delete","Delete this equation type").click(function(){
								deleteNode(id)
							})
						))
					)
				)
			}
			$container.empty().append(
				$("
").append(
					writeThead(),
					writeTfoot(),
					$("
").append(
						// equations
						$("
").append(
							theadLayout.columns.map(id=>"$$"+data[id].equation+"$$	")
						),
						// properties
						$("
").append(
							theadLayout.columns.map(id=>$("").append(
								writeData('properties',id)
							))
						),
						// solutions
						$("
").append(
							theadLayout.columns.map(id=>$("").append(
								writeData('solutions',id)
							))
						)
					)
				),
				"
General notes:
",
				$("
").append(
					`<li>how to read the diagram: <ul>`+
						`<li>equation type names are written among arrows that show the relationship between types</li>`+
						`<li>properties of an equation type are listed in the table column below its name</li>`+
						`
every property of the equation type on the tip of the arrow  is also true for the equation type on the other end of the arrow (like class diagram)
`+
					`</ul></li>`,
					`<li>all functions have to be continuous on the interval of interest</li>`,
					`<li>\\( \\int\\!f(t)\\,\\mathrm{d}t + C \\) is a family of antiderivatives of \\( f(t) \\) with parameter \\( C \\)</li>`,
					`<li>\\( \\int\\!f(t)\\,\\mathrm{d}t \\) is any single antiderivative of \\( f(t) \\)`
				)
			)
			MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
		}
		writeTable()
	})
})
<commit_msg>prevent default keyboard actions when using popup menus<commit_after>'use strict'

const TheadLayout=require('./thead-layout')
const data=require('./data')

$(function(){
	$('.crnx-ode-properties').each(function(){
		const $container=$(this)
		const dag={}, idag={}
		const selectedNodes={}
		for (let id in data) {
			if (data[id].importance<=1) {
				selectedNodes[id]=true
			}
			dag[id]=data[id].parents
			idag[id]={}
		}
		for (let id in data) {
			for (let pid in dag[id]) {
				idag[pid][id]=true
			}
		}
		let theadLayout=new TheadLayout(dag,selectedNodes)
		const breadthWalk=(graph,id)=>{
			const result=[]
			const visited={}
			const queue=[id]
			while (queue.length>0) {
				id=queue.shift()
				if (visited[id]) {
					continue
				}
				visited[id]=true
				if (!selectedNodes[id]) {
					result.push(id)
				}
				queue.push(...Object.keys(graph[id]).sort())
			}
			return result
		}
		const writeButton=(text,tip)=>{
			const $button=$(`<button type='button'><span>${text}</span></button>`)
			if (tip!==undefined) {
				$button.attr('title',tip)
			}
			return $button
		}
		const writeData=(name,id)=>{
			const output=[]

			// mark ancestors of visible ancestors as visited
			const visited={}
			const markAncestors=id=>{
				if (visited[id]) return
				visited[id]=true
				Object.keys(data[id].parents).forEach(pid=>{
					markAncestors(pid)
				})
			}
			const reachAncestors=id=>{
				if (visited[id]) return
				Object.keys(data[id].parents).forEach(pid=>{
					if (selectedNodes[pid]) {
						markAncestors(pid)
					} else {
						reachAncestors(pid)
					}
				})
			}
			reachAncestors(id)

			const overrides={}
			const raiseOverride=iid=>{
				if (overrides[iid]===undefined) {
					overrides[iid]=0
				}
				overrides[iid]++
			}
			const lowerOverride=iid=>{
				overrides[iid]--
			}
			const rec=cid=>{
				visited[cid]=true
				const forOverrides=fn=>{
					if (data[cid][name]) {
						data[cid][name].forEach(item=>{
							item.forEach(section=>{
								const type=section[0]
								if (section[0]=='override') {
									const iids=section[1]
									iids.forEach(iid=>{
										fn(iid)
									})
								}
							})
						})
					}
				}
				// recursion on nodes that are not selected for display
				forOverrides(raiseOverride)
				Object.keys(data[cid].parents).sort().forEach(pid=>{
					if (!visited[pid]) {
						rec(pid)
					}
				})
				forOverrides(lowerOverride)
				// output of things that are not overridden by children
				if (data[cid][name]) {
					data[cid][name].forEach(item=>{
						let skip=false
						item.forEach(section=>{
							const type=section[0]
							if (type=='id') {
								const iid=section[1]
								if (overrides[iid]) {
									skip=true
								}
							}
						})
						if (skip) return
						const outItem=[]
						item.forEach(section=>{
							const type=section[0]
							if (type=='id' || type=='override') return
							if (type=='form') {
								if (cid!=id) {
									outItem.push(['note',[
										"when equation is written as "+data[cid].htmlName+":",
										"\\["+data[cid].equation+"\\]",
									]])
								}
							} else {
								outItem.push(section)
							}
						})
						output.push(outItem)
					})
				}
			}
			rec(id)
			if (output.length==0) return $()
			return $("
").append(output.map(item=>{
				return $("
").append(item.map(section=>{
					const type=section[0], contents=section[1]
					const $section=$(`<div class='${type}'>`).append(contents.map(line=>{
						if (type=='title') {
							return $(`<div><em>${line}</em>:</div>`)
						} else {
							return $(`<div>${line}</div>`)
						}
					}))
					if (type=='detail') {
						const $b1=writeButton("Open","Expand details")
						const $b2=writeButton("Open","Expand details")
						$section.prepend($b1).append($b2)
						const $bs=$b1.add($b2)
						$bs.click(function(){
							if (!$section.hasClass('open')) {
								$section.addClass('open')
								$bs.html("Close").attr('title',"Collapse details")
							} else {
								$section.removeClass('open')
								$bs.html("Open").attr('title',"Expand details")
							}
						})
					}
					return $section
				}))
			}))
		}
		const writeTable=()=>{
			let $attachedMenu, $attachedToButton, attachedDirection, attachTimeoutId
			const deleteNode=id=>{
				delete selectedNodes[id]
				theadLayout=new TheadLayout(dag,selectedNodes)
				writeTable()
			}
			const addNode=id=>{
				selectedNodes[id]=true
				theadLayout=new TheadLayout(dag,selectedNodes)
				writeTable()
			}
			const setCellClasses=($cell,cell)=>{
				;['b','t','bt','rl','rt','bl'].forEach(dir=>{
					if (cell[dir]) {
						$cell.addClass(dir)
					}
				})
			}
			const writeTheadButton=($cell,text,tip,dir,nodes)=>{
				const keyCodeEnter=13
				const keyCodeSpace=32
				const keyCodeUp=38
				const keyCodeDown=40
				const $button=writeButton(text,tip).addClass(dir).click(function(){
					const removeAttachedMenu=()=>{
						$attachedToButton.removeClass('attached-t attached-b')
						$attachedMenu.remove()
						clearTimeout(attachTimeoutId)
						$attachedToButton=undefined
						$attachedMenu=undefined
						attachedDirection=undefined
						attachTimeoutId=undefined
					}
					const startAttachingMenu=()=>{
						$attachedToButton=$button
						$attachedMenu=$("
").append(nodes.map(
							id=>$("
"+data[id].name+"
").click(function(){
								addNode(id)
							}).keydown(function(ev){
								if (!attachedDirection) return // not yet decided on attach direction (this can't happen)
								if (ev.keyCode==keyCodeUp) {
									const $toFocus=$(this).prev()
									if ($toFocus.length) {
										$toFocus.focus()
										return false
									} else if (attachedDirection=='b') {
										$button.focus()
										return false
									}
								} else if (ev.keyCode==keyCodeDown) {
									const $toFocus=$(this).next()
									if ($toFocus.length) {
										$toFocus.focus()
										return false
									} else if (attachedDirection=='t') {
										$button.focus()
										return false
									}
								} else if (ev.keyCode==keyCodeEnter || ev.keyCode==keyCodeSpace) {
									addNode(id)
									return false
								}
							})
						))
						$cell.append($attachedMenu)
						attachTimeoutId=setTimeout(()=>{ // can calculate height only after it's displayed
							const mh=$attachedMenu.outerHeight()
							const mo=$attachedMenu.offset()
							const bh=$button.outerHeight()
							const bo=$button.offset()
							let t=bo.top-mh+1
							if (dir!='t' || t<0) { // want below or doesn't fit to screen above the button
								t=bo.top+bh-1
								attachedDirection='b'
							} else {
								$attachedMenu.insertBefore($button)
								attachedDirection='t'
							}
							$attachedMenu.offset({
								top: t,
								left: mo.left,
							})
							$attachedMenu.addClass('attached')
							$button.addClass('attached-'+attachedDirection)
						},0)
					}
					if (!$button.is($attachedToButton)) {
						if ($attachedToButton) {
							removeAttachedMenu() // close menu opened elsewhere
						}
						startAttachingMenu()
					} else {
						removeAttachedMenu() // close menu here
					}
				}).keydown(function(ev){
					if ($button.is($attachedToButton)) {
						if (ev.keyCode==keyCodeUp && attachedDirection=='t') {
							$attachedMenu.children().last().focus()
							return false
						} else if (ev.keyCode==keyCodeDown && attachedDirection=='b') {
							$attachedMenu.children().first().focus()
							return false
						}
					}
				})
				return $button
			}
			const writeTheadCell=cell=>{
				const $cell=$("")
				setCellClasses($cell,cell)
				if (cell.node!==undefined) {
					$cell.append(data[cell.node].htmlName!==undefined
						? data[cell.node].htmlName
						: data[cell.node].name
					)
					const parents=breadthWalk(dag,cell.node).reverse()
					let $parents
					if (parents.length>0) {
						$cell.append(
							" ",
							writeTheadButton($cell,"Add parent","Add one of supertypes of this equation type",'t',parents)
						)
					}
					const children=breadthWalk(idag,cell.node)
					if (children.length>0) {
						$cell.append(
							" ",
							writeTheadButton($cell,"Add child","Add one of subtypes of this equation type",'b',children)
						)
					}
				}
				return $cell
			}
			const writeThead=()=>{
				const $thead=$("
")
				for (let i=0;i<theadLayout.nodeLayers.length;i++) {
					$thead.append(
						$("
").append(
							theadLayout.nodeLayers[i].map(writeTheadCell)
						)
					)
					if (i<theadLayout.arcLayers.length) {
						$thead.append(
							theadLayout.arcLayers[i].map(row=>$("
").append(
								row.map(cell=>{
									const $cell=$("")
									setCellClasses($cell,cell)
									return $cell
								})
							))
						)
					}
				}
				return $thead
			}
			const writeTfoot=()=>{
				if (theadLayout.columns.length<=1) {
					return $()
				}
				return $("
").append(
					$("
").append(
						theadLayout.columns.map(id=>$("").append(
							writeButton("Delete","Delete this equation type").click(function(){
								deleteNode(id)
							})
						))
					)
				)
			}
			$container.empty().append(
				$("
").append(
					writeThead(),
					writeTfoot(),
					$("
").append(
						// equations
						$("
").append(
							theadLayout.columns.map(id=>"$$"+data[id].equation+"$$	")
						),
						// properties
						$("
").append(
							theadLayout.columns.map(id=>$("").append(
								writeData('properties',id)
							))
						),
						// solutions
						$("
").append(
							theadLayout.columns.map(id=>$("").append(
								writeData('solutions',id)
							))
						)
					)
				),
				"
General notes:
",
				$("
").append(
					`<li>how to read the diagram: <ul>`+
						`<li>equation type names are written among arrows that show the relationship between types</li>`+
						`<li>properties of an equation type are listed in the table column below its name</li>`+
						`
every property of the equation type on the tip of the arrow  is also true for the equation type on the other end of the arrow (like class diagram)
`+
					`</ul></li>`,
					`<li>all functions have to be continuous on the interval of interest</li>`,
					`<li>\\( \\int\\!f(t)\\,\\mathrm{d}t + C \\) is a family of antiderivatives of \\( f(t) \\) with parameter \\( C \\)</li>`,
					`<li>\\( \\int\\!f(t)\\,\\mathrm{d}t \\) is any single antiderivative of \\( f(t) \\)`
				)
			)
			MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
		}
		writeTable()
	})
})
<|endoftext|>


<commit_before>'use strict'

const TheadLayout=require('./thead-layout')
const data=require('./data')

$(function(){
	$('.crnx-ode-properties').each(function(){
		const $container=$(this)
		const dag={}, idag={}
		const selectedNodes={}
		for (let id in data) {
			selectedNodes[id]=true
			dag[id]=data[id].parents
			for (let pid in dag[id]) {
				if (idag[pid]===undefined) {
					idag[pid]={}
				}
				idag[pid][id]=true
			}
		}
		let theadLayout=new TheadLayout(dag,selectedNodes)
		const writeButton=(text,tip)=>{
			const $button=$(`<button type='button'><span>${text}</span></button>`)
			if (tip!==undefined) {
				$button.attr('title',tip)
			}
			return $button
		}
		const writeTable=()=>{
			const setCellClasses=($cell,cell)=>{
				;['b','t','bt','rl','rt','bl'].forEach(dir=>{
					if (cell[dir]) {
						$cell.addClass(dir)
					}
				})
			}
			const writeThead=()=>{
				const $thead=$("
")
				for (let i=0;i<theadLayout.nodeLayers.length;i++) {
					$thead.append(
						$("
").append(
							theadLayout.nodeLayers[i].map(cell=>{
								const $cell=$("")
								if (cell.node!==undefined) {
									$cell.append(data[cell.node].name)
								}
								setCellClasses($cell,cell)
								if (cell.t) {
									$cell.append(
										" ",
										writeButton("Add parent","Add one of parents of this node").addClass('t')
									)
								}
								if (cell.b) {
									$cell.append(
										" ",
										writeButton("Add child","Add one of children of this node").addClass('b')
									)
								}
								return $cell
							})
						)
					)
					if (i<theadLayout.arcLayers.length) {
						$thead.append(
							theadLayout.arcLayers[i].map(row=>$("
").append(
								row.map(cell=>{
									const $cell=$("")
									setCellClasses($cell,cell)
									return $cell
								})
							))
						)
					}
				}
				return $thead
			}
			const writeTfoot=()=>{
				if (theadLayout.columns.length<=1) {
					return $()
				}
				return $("
").append(
					$("
").append(
						theadLayout.columns.map(id=>$("").append(
							writeButton("Delete","Delete this node").click(function(){
								delete selectedNodes[id]
								theadLayout=new TheadLayout(dag,selectedNodes)
								writeTable()
							})
						))
					)
				)
			}
			$container.empty().append(
				$("
").append(
					writeThead(),
					writeTfoot(),
					$("
").append(
						$("
").append(
							theadLayout.columns.map(id=>"$$"+data[id].equation+"$$	")
						)
					)
				)
			)
			MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
		}
		writeTable()
	})
})
<commit_msg>display add node buttons only if there are nodes to add<commit_after>'use strict'

const TheadLayout=require('./thead-layout')
const data=require('./data')

$(function(){
	$('.crnx-ode-properties').each(function(){
		const $container=$(this)
		const dag={}, idag={}
		const selectedNodes={}
		for (let id in data) {
			selectedNodes[id]=true
			dag[id]=data[id].parents
			idag[id]={}
		}
		for (let id in data) {
			for (let pid in dag[id]) {
				idag[pid][id]=true
			}
		}
		let theadLayout=new TheadLayout(dag,selectedNodes)
		const breadthWalk=(graph,id)=>{
			const result=[]
			const visited={}
			const queue=[id]
			while (queue.length>0) {
				id=queue.shift()
				if (visited[id]) {
					continue
				}
				visited[id]=true
				if (!selectedNodes[id]) {
					result.push(id)
				}
				queue.push(...Object.keys(graph[id]).sort())
			}
			return result
		}
		const writeButton=(text,tip)=>{
			const $button=$(`<button type='button'><span>${text}</span></button>`)
			if (tip!==undefined) {
				$button.attr('title',tip)
			}
			return $button
		}
		const writeTable=()=>{
			const setCellClasses=($cell,cell)=>{
				;['b','t','bt','rl','rt','bl'].forEach(dir=>{
					if (cell[dir]) {
						$cell.addClass(dir)
					}
				})
			}
			const writeTheadCell=cell=>{
				const $cell=$("")
				setCellClasses($cell,cell)
				if (cell.node!==undefined) {
					$cell.append(data[cell.node].name)
					const parents=breadthWalk(dag,cell.node)
					if (parents.length>0) {
						$cell.append(
							" ",
							writeButton("Add parent","Add one of parents of this node").addClass('t')
						)
					}
					const children=breadthWalk(idag,cell.node)
					if (children.length>0) {
						$cell.append(
							" ",
							writeButton("Add child","Add one of children of this node").addClass('b')
						)
					}
				}
				return $cell
			}
			const writeThead=()=>{
				const $thead=$("
")
				for (let i=0;i<theadLayout.nodeLayers.length;i++) {
					$thead.append(
						$("
").append(
							theadLayout.nodeLayers[i].map(writeTheadCell)
						)
					)
					if (i<theadLayout.arcLayers.length) {
						$thead.append(
							theadLayout.arcLayers[i].map(row=>$("
").append(
								row.map(cell=>{
									const $cell=$("")
									setCellClasses($cell,cell)
									return $cell
								})
							))
						)
					}
				}
				return $thead
			}
			const writeTfoot=()=>{
				if (theadLayout.columns.length<=1) {
					return $()
				}
				return $("
").append(
					$("
").append(
						theadLayout.columns.map(id=>$("").append(
							writeButton("Delete","Delete this node").click(function(){
								delete selectedNodes[id]
								theadLayout=new TheadLayout(dag,selectedNodes)
								writeTable()
							})
						))
					)
				)
			}
			$container.empty().append(
				$("
").append(
					writeThead(),
					writeTfoot(),
					$("
").append(
						$("
").append(
							theadLayout.columns.map(id=>"$$"+data[id].equation+"$$	")
						)
					)
				)
			)
			MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
		}
		writeTable()
	})
})
<|endoftext|>