<commit_before>(function() {
  'use strict';
  var module = angular.module('ngGeolocator', ['ngGeolocatorConstants']);

  /**
   * @constructs Locator
   * @param {google.maps.Marker} marker - The marker on the map that indicates the user's location
   */
  function Locator(marker) {
    this.marker = marker;
  }

  /**
   * @typedef LatLng
   * @type {Object}
   * @property {number} lat - The latitude.
   * @property {number} lng - The longitude.
   */

  /**
   * @returns {LatLng} The current user's selected position.
   */
  Locator.prototype.getLocation = function() {
    var location = this.marker.getPosition();
    return {
      lat: location.lat(),
      lng: location.lng(),
    };
  };

  module.factory('ngGeolocator', ['$window', '$q', '$timeout', 'geolocationIndicator',
    function($window, $q, $timeout, geolocationIndicator) {
      var mapsAPIPromise, geolocationPromise;

      /**
       * Asynchorously loads the Google Maps API by appending it's script to the
       * DOM body element.
       *
       * @param {string} [key] The Google Maps API key.
       * @returns {Promise} A promise that will be resolved when Google Maps has
       * been initialized.
       */
      function loadMapsAPI(key) {
        if (!mapsAPIPromise) {
          var mapsDefer = $q.defer();
          mapsAPIPromise = mapsDefer.promise;
          var script = document.createElement('script');
          script.type = 'text/javascript';
          script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&callback=googleMapsInitialized';
          if (key) {
            script.src += '&key=' + key;
          }
          $window.googleMapsInitialized = mapsDefer.resolve;
          $window.document.body.appendChild(script);
        }
        return mapsAPIPromise;
      }

      /**
       * Tries to get the users current location using the HTML5 Geolocation API
       * and returns a promise for the position response from the Geolocation API.
       * If the users declines or does not respond in time, the map promise will
       * be used to draw an infobox on the map if/when the map is created and this
       * methods promise will be rejected the failure message.
       *
       * @param {Promise} mapPromise
       * @returns {Promise}
       */
      function loadUserLocation(mapPromise) {
        if (!$window.navigator.geolocation) {
          return handleNoGeolocation(mapPromise, 'Your browser doesn\'t support geolocation.');
        }
        if (!geolocationPromise) {
          var geolocationDefer = $q.defer();
          geolocationPromise = geolocationDefer.promise;
          $window.navigator.geolocation.getCurrentPosition(geolocationDefer.resolve, geolocationDefer.reject);
          var timeoutPromise = $timeout(function() {
            geolocationDefer.reject('Timed out');
          }, 10000);
          geolocationPromise.then(function() {
            $timeout.cancel(timeoutPromise);
          });
        }
        return geolocationPromise.catch(function() {
          return handleNoGeolocation(mapPromise, 'The Geolocation service failed.');
        });
      }

      function handleNoGeolocation(mapPromise, error) {
        mapPromise.then(function(map) {
          var options = {
            position: new $window.google.maps.LatLng(60, 105),
            content: 'Error: '+error,
          };
          var infoWindow = new $window.google.maps.InfoWindow(options);
          infoWindow.setMap(map);
          map.setCenter(options.position);
        });
        return $q.reject(error);
      }

      /**
       * Create a static marker for the given position and draw a circle with the accuracy
       * radius around it.
       */
      function createGeoEstimateElements(map, position) {
        var pos = converToLatLng(position);
        var marker = new $window.google.maps.Marker({
          clickable: false,
          cursor: 'pointer',
          draggable: false,
          flat: true,
          icon: {
            url: geolocationIndicator,
            size: new $window.google.maps.Size(34, 34),
            scaledSize: new $window.google.maps.Size(17, 17),
            origin: new $window.google.maps.Point(0, 0),
            anchor: new $window.google.maps.Point(8, 8)
          },
          title: 'Current location',
          zIndex: 2,
          position: pos,
        });
        marker.setMap(map);
        var circle = new $window.google.maps.Circle({
          clickable: false,
          radius: position.coords.accuracy,
          strokeColor: '1bb6ff',
          strokeOpacity: 0.4,
          fillColor: '61a0bf',
          fillOpacity: 0.4,
          strokeWeight: 1,
          zIndex: 1,
          center: pos,
        });
        circle.setMap(map);
      }

      function centerMapOn(map, position) {
        var pos = converToLatLng(position);
        map.setCenter(pos);
      }

      function converToLatLng(position) {
        return new $window.google.maps.LatLng(position.coords.latitude, position.coords.longitude);
      }

      function initMap(canvasID) {
        var mapOptions = {
          zoom: 17,
        };
        return new $window.google.maps.Map($window.document.getElementById(canvasID), mapOptions);
      }

      function createLocatorMarker(map, position) {
        var pos = converToLatLng(position);
        return new $window.google.maps.Marker({
          draggable: true,
          zIndex: 3,
          map: map,
          position: pos,
        });
      }
      return {
        /**
         * create will initialize google maps, if it isn't already initialized, and
         * will then draw a map on the specified canvasID. Once the user has
         * accepted to share their location, the map will be centered to that location
         * and a marker will be displayed that the user can move to confirm/specify
         * their actual location. This marker will then be used to create a new
         * {@link Locator} object which will then be used to resolve the returned promise.
         *
         * @param {string} canvasID - The elemt ID of the canvas to load the map onto.
         * @param {string} [key]    - Google Maps API key to be used for initializing the API.
         * @returns {Promise.<Locator>}
         */
        create: function(canvasID, key) {
          var mapsAPIPromise = loadMapsAPI(key);
          var mapPromise = mapsAPIPromise.then(function() {
            return initMap(canvasID);
          });
          var userLocationPromise = loadUserLocation(mapPromise);

          return $q.all({
            map: mapPromise,
            pos: userLocationPromise,
          }).then(function(asyncResults) {
            var map = asyncResults.map;
            var pos = asyncResults.pos;

            var locatorMarker = createLocatorMarker(map, pos);
            centerMapOn(map, pos);
            createGeoEstimateElements(map, pos);
            return new Locator(locatorMarker);
          });
        },
      };
    }
  ]);
})();
<commit_msg>update Locator.getLocation definition to wrapped in the initializer<commit_after>(function() {
  'use strict';
  var module = angular.module('ngGeolocator', ['ngGeolocatorConstants']);

  /**
   * @constructs Locator
   * @param {google.maps.Marker} marker - The marker on the map that indicates the user's location
   */
  function Locator(marker) {
    /**
     * @typedef LatLng
     * @type {Object}
     * @property {number} lat - The latitude.
     * @property {number} lng - The longitude.
     */

    /**
     * @returns {LatLng} The current user's selected position.
     */
    this.getLocation = function() {
      var location = marker.getPosition();
      return {
        lat: location.lat(),
        lng: location.lng(),
      };
    };
  }

  module.factory('ngGeolocator', ['$window', '$q', '$timeout', 'geolocationIndicator',
    function($window, $q, $timeout, geolocationIndicator) {
      var mapsAPIPromise, geolocationPromise;

      /**
       * Asynchorously loads the Google Maps API by appending it's script to the
       * DOM body element.
       *
       * @param {string} [key] The Google Maps API key.
       * @returns {Promise} A promise that will be resolved when Google Maps has
       * been initialized.
       */
      function loadMapsAPI(key) {
        if (!mapsAPIPromise) {
          var mapsDefer = $q.defer();
          mapsAPIPromise = mapsDefer.promise;
          var script = document.createElement('script');
          script.type = 'text/javascript';
          script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp&callback=googleMapsInitialized';
          if (key) {
            script.src += '&key=' + key;
          }
          $window.googleMapsInitialized = mapsDefer.resolve;
          $window.document.body.appendChild(script);
        }
        return mapsAPIPromise;
      }

      /**
       * Tries to get the users current location using the HTML5 Geolocation API
       * and returns a promise for the position response from the Geolocation API.
       * If the users declines or does not respond in time, the map promise will
       * be used to draw an infobox on the map if/when the map is created and this
       * methods promise will be rejected the failure message.
       *
       * @param {Promise} mapPromise
       * @returns {Promise}
       */
      function loadUserLocation(mapPromise) {
        if (!$window.navigator.geolocation) {
          return handleNoGeolocation(mapPromise, 'Your browser doesn\'t support geolocation.');
        }
        if (!geolocationPromise) {
          var geolocationDefer = $q.defer();
          geolocationPromise = geolocationDefer.promise;
          $window.navigator.geolocation.getCurrentPosition(geolocationDefer.resolve, geolocationDefer.reject);
          var timeoutPromise = $timeout(function() {
            geolocationDefer.reject('Timed out');
          }, 10000);
          geolocationPromise.then(function() {
            $timeout.cancel(timeoutPromise);
          });
        }
        return geolocationPromise.catch(function() {
          return handleNoGeolocation(mapPromise, 'The Geolocation service failed.');
        });
      }

      function handleNoGeolocation(mapPromise, error) {
        mapPromise.then(function(map) {
          var options = {
            position: new $window.google.maps.LatLng(60, 105),
            content: 'Error: '+error,
          };
          var infoWindow = new $window.google.maps.InfoWindow(options);
          infoWindow.setMap(map);
          map.setCenter(options.position);
        });
        return $q.reject(error);
      }

      /**
       * Create a static marker for the given position and draw a circle with the accuracy
       * radius around it.
       */
      function createGeoEstimateElements(map, position) {
        var pos = converToLatLng(position);
        var marker = new $window.google.maps.Marker({
          clickable: false,
          cursor: 'pointer',
          draggable: false,
          flat: true,
          icon: {
            url: geolocationIndicator,
            size: new $window.google.maps.Size(34, 34),
            scaledSize: new $window.google.maps.Size(17, 17),
            origin: new $window.google.maps.Point(0, 0),
            anchor: new $window.google.maps.Point(8, 8)
          },
          title: 'Current location',
          zIndex: 2,
          position: pos,
        });
        marker.setMap(map);
        var circle = new $window.google.maps.Circle({
          clickable: false,
          radius: position.coords.accuracy,
          strokeColor: '1bb6ff',
          strokeOpacity: 0.4,
          fillColor: '61a0bf',
          fillOpacity: 0.4,
          strokeWeight: 1,
          zIndex: 1,
          center: pos,
        });
        circle.setMap(map);
      }

      function centerMapOn(map, position) {
        var pos = converToLatLng(position);
        map.setCenter(pos);
      }

      function converToLatLng(position) {
        return new $window.google.maps.LatLng(position.coords.latitude, position.coords.longitude);
      }

      function initMap(canvasID) {
        var mapOptions = {
          zoom: 17,
        };
        return new $window.google.maps.Map($window.document.getElementById(canvasID), mapOptions);
      }

      function createLocatorMarker(map, position) {
        var pos = converToLatLng(position);
        return new $window.google.maps.Marker({
          draggable: true,
          zIndex: 3,
          map: map,
          position: pos,
        });
      }
      return {
        /**
         * create will initialize google maps, if it isn't already initialized, and
         * will then draw a map on the specified canvasID. Once the user has
         * accepted to share their location, the map will be centered to that location
         * and a marker will be displayed that the user can move to confirm/specify
         * their actual location. This marker will then be used to create a new
         * {@link Locator} object which will then be used to resolve the returned promise.
         *
         * @param {string} canvasID - The elemt ID of the canvas to load the map onto.
         * @param {string} [key]    - Google Maps API key to be used for initializing the API.
         * @returns {Promise.<Locator>}
         */
        create: function(canvasID, key) {
          var mapsAPIPromise = loadMapsAPI(key);
          var mapPromise = mapsAPIPromise.then(function() {
            return initMap(canvasID);
          });
          var userLocationPromise = loadUserLocation(mapPromise);

          return $q.all({
            map: mapPromise,
            pos: userLocationPromise,
          }).then(function(asyncResults) {
            var map = asyncResults.map;
            var pos = asyncResults.pos;

            var locatorMarker = createLocatorMarker(map, pos);
            centerMapOn(map, pos);
            createGeoEstimateElements(map, pos);
            return new Locator(locatorMarker);
          });
        },
      };
    }
  ]);
})();
<|endoftext|>


<commit_before>/*
 *  Majiang.Dev.Analog
 */
"use strict";

const fs   = require('fs');
const path = require('path');
const zlib = require('zlib');

const Majiang = require('../../majiang');

module.exports = class AnaLog {

constructor() {

    this._result = {
        n_game:     0,
        n_ju:       0,
        n_hule:     0,
        n_beirong:  0,
        n_lizhi:    0,
        n_fulou:    0,
        n_pingju:   0,
        sum_defen:  0,
        hupai:      {},
        pingju:     {},
    };
}

analyze(id, paipu) {
    this._id = id;
    for (let log of paipu.log) {
        this.log(log);
    }
    this._result.n_game++;
}

log(log) {

    let qipai = log[0].qipai;
    this._jushu = ['東','南','西','北'][qipai.zhuangfeng]
                + ['一','二','三','四'][qipai.jushu] + '局 '
                + qipai.changbang + '本場';

    for (let data of log) {
        if      (data.qipai)    this.qipai   (data.qipai);
        else if (data.zimo)     this.zimo    (data.zimo);
        else if (data.dapai)    this.dapai   (data.dapai);
        else if (data.fulou)    this.fulou   (data.fulou);
        else if (data.gang)     this.gang    (data.gang);
        else if (data.gangzimo) this.gangzimo(data.gangzimo);
        else if (data.kaigang)  this.kaigang (data.kaigang);
        else if (data.hule)     this.hule    (data.hule);
        else if (data.pingju)   this.pingju  (data.pingju);
    }
    this._result.n_ju++;
}

qipai(qipai) {

    this._game = {
        zhuangfeng: qipai.zhuangfeng,
        jushu:      qipai.jushu,
        changbang:  qipai.changbang,
        shoupai:    [],
        he:         [],
        baopai:     [qipai.baopai],
        paishu:     136 - 13 * 4 -14,
        lunban:     0,
    };
    for (let l = 0; l < 4; l++) {
        this._game.shoupai[l] = Majiang.Shoupai.fromString(qipai.shoupai[l]);
        this._game.he[l]      = new Majiang.He();
    }
}

zimo(zimo) {
    this._game.paishu--;
    this._game.shoupai[zimo.l].zimo(zimo.p);
    this._game.lunban = zimo.l;
}

dapai(dapai) {
    this._game.shoupai[dapai.l].dapai(dapai.p);
    this._game.he[dapai.l].dapai(dapai.p);
    if (dapai.p.substr(-1) == '*') this._result.n_lizhi++;
}

fulou(fulou) {
    let d = fulou.m.match(/[\+\=\-]/)[0];
    this._game.shoupai[fulou.l].fulou(fulou.m);
    this._game.he[this._game.lunban].fulou(d);
    this._game.lunban = fulou.l;
    if (this._game.shoupai[fulou.l]._fulou
            .filter(m=>m.match(/[\+\=\-]/)).length == 1) this._result.n_fulou++;
}

gang(gang) {
    if (gang.m.match(/^[mpsz]\d{4}$/)) {
        let s = gang.m[0], n = +gang.m[1] || 5;
        this._game.shoupai[gang.l].gang(s+n);
    }
    else {
        let s = gang.m[0], n = gang.m.substr(-1);
        this._game.shoupai[gang.l].gang(s+n);
    }
}

gangzimo(gangzimo) {
    this._game.paishu--;
    this._game.shoupai[gangzimo.l].zimo(gangzimo.p);
}

kaigang(kaigang) {
    this._game.baopai.push(kaigang.baopai);
}

hule(hule) {

    this._result.n_hule++;
    if (hule.baojia != null) this._result.n_beirong++;
    this._result.sum_defen += hule.defen;

    for (let hupai of hule.hupai) {
        if (hupai.fanshu == 0) continue;
        if (! this._result.hupai[hupai.name])
            this._result.hupai[hupai.name] = { n: 0, fanshu: 0 };
        this._result.hupai[hupai.name].n++;
        this._result.hupai[hupai.name].fanshu += hupai.fanshu;
    }
}

pingju(pingju) {

    this._result.n_pingju++;

    if (! this._result.pingju[pingju.name])
        this._result.pingju[pingju.name] = 0;
    this._result.pingju[pingju.name]++;
}

analyze_file(filename) {

    let id, paipu;
    if (filename.match(/\.json\.gz$/)) {
        id = filename.replace(/^.*\//,'').replace(/\.json\.gz$/,'');
        paipu = JSON.parse(
                        zlib.gunzipSync(
                            fs.readFileSync(path.join(filename))
                        ).toString());
    }
    else if (filename.match(/\.json$/)) {
        id = filename.replace(/^.*\//,'').replace(/\.json$/,'');
        paipu = JSON.parse(fs.readFileSync(path.join(filename)));
    }
    else return;

    this.analyze(id, paipu);

    return this._result;
}

analyze_dir(dirname, n_try) {

    let logs = fs.readdirSync(dirname)
                    .filter(n=>n.match(/\.json(?:\.gz)?$/)).sort();
    if (n_try == null) n_try = logs.length;

    console.log(`[${Math.min(n_try, logs.length)}]`, new Date().toTimeString());

    while (n_try && logs.length) {

        let filename = logs.shift();
        try {
            let rv = this.analyze_file(path.join(dirname, filename));
            if (! rv) continue;
        }
        catch(e) {
            console.error(`${filename}: ${e}`);
        }

        n_try--;
        if (n_try % 10000 == 0 || logs.length == 0) {
            console.log(`[${Math.min(n_try, logs.length)}]`,
                        new Date().toTimeString());
        }
    }
    return this._result;
}

static analyze(filename, n_try) {

    filename = filename || './';
    const analog = new AnaLog();
    if (fs.statSync(filename).isDirectory())
            analog.analyze_dir(filename, n_try);
    else    analog.analyze_file(filename);

    return analog._result;
}

}
<commit_msg>#24 牌譜解析ツールが牌譜読み込み用URLを生成できるようにした<commit_after>/*
 *  Majiang.Dev.Analog
 */
"use strict";

const fs   = require('fs');
const path = require('path');
const zlib = require('zlib');

const Majiang = require('../../majiang');

module.exports = class AnaLog {

constructor() {

    this._result = {
        n_game:     0,
        n_ju:       0,
        n_hule:     0,
        n_beirong:  0,
        n_lizhi:    0,
        n_fulou:    0,
        n_pingju:   0,
        sum_defen:  0,
        hupai:      {},
        pingju:     {},
    };
}

analyze(id, paipu) {
    this._id = id;
    this._ju = 0;
    this._qijia = paipu.qijia;
    for (let log of paipu.log) {
        this.log(log);
    }
    this._result.n_game++;
}

log(log) {

    let qipai = log[0].qipai;
    this._jushu = ['東','南','西','北'][qipai.zhuangfeng]
                + ['一','二','三','四'][qipai.jushu] + '局 '
                + qipai.changbang + '本場';

    for (let data of log) {
        if      (data.qipai)    this.qipai   (data.qipai);
        else if (data.zimo)     this.zimo    (data.zimo);
        else if (data.dapai)    this.dapai   (data.dapai);
        else if (data.fulou)    this.fulou   (data.fulou);
        else if (data.gang)     this.gang    (data.gang);
        else if (data.gangzimo) this.gangzimo(data.gangzimo);
        else if (data.kaigang)  this.kaigang (data.kaigang);
        else if (data.hule)     this.hule    (data.hule);
        else if (data.pingju)   this.pingju  (data.pingju);
    }
    this._ju++;
    this._result.n_ju++;
}

qipai(qipai) {

    this._game = {
        zhuangfeng: qipai.zhuangfeng,
        jushu:      qipai.jushu,
        changbang:  qipai.changbang,
        shoupai:    [],
        he:         [],
        baopai:     [qipai.baopai],
        paishu:     136 - 13 * 4 -14,
        lunban:     0,
        player_id:  [0,1,2,3].map(l=>(this._qijia + qipai.jushu + l) % 4),
    };
    for (let l = 0; l < 4; l++) {
        this._game.shoupai[l] = Majiang.Shoupai.fromString(qipai.shoupai[l]);
        this._game.he[l]      = new Majiang.He();
    }
}

zimo(zimo) {
    this._game.paishu--;
    this._game.shoupai[zimo.l].zimo(zimo.p);
    this._game.lunban = zimo.l;
}

dapai(dapai) {
    this._game.shoupai[dapai.l].dapai(dapai.p);
    this._game.he[dapai.l].dapai(dapai.p);
    if (dapai.p.substr(-1) == '*') this._result.n_lizhi++;
}

fulou(fulou) {
    let d = fulou.m.match(/[\+\=\-]/)[0];
    this._game.shoupai[fulou.l].fulou(fulou.m);
    this._game.he[this._game.lunban].fulou(d);
    this._game.lunban = fulou.l;
    if (this._game.shoupai[fulou.l]._fulou
            .filter(m=>m.match(/[\+\=\-]/)).length == 1) this._result.n_fulou++;
}

gang(gang) {
    if (gang.m.match(/^[mpsz]\d{4}$/)) {
        let s = gang.m[0], n = +gang.m[1] || 5;
        this._game.shoupai[gang.l].gang(s+n);
    }
    else {
        let s = gang.m[0], n = gang.m.substr(-1);
        this._game.shoupai[gang.l].gang(s+n);
    }
}

gangzimo(gangzimo) {
    this._game.paishu--;
    this._game.shoupai[gangzimo.l].zimo(gangzimo.p);
}

kaigang(kaigang) {
    this._game.baopai.push(kaigang.baopai);
}

hule(hule) {

    this._result.n_hule++;
    if (hule.baojia != null) this._result.n_beirong++;
    this._result.sum_defen += hule.defen;

    for (let hupai of hule.hupai) {
        if (hupai.fanshu == 0) continue;
        if (! this._result.hupai[hupai.name])
            this._result.hupai[hupai.name] = { n: 0, fanshu: 0 };
        this._result.hupai[hupai.name].n++;
        this._result.hupai[hupai.name].fanshu += hupai.fanshu;
    }
}

pingju(pingju) {

    this._result.n_pingju++;

    if (! this._result.pingju[pingju.name])
        this._result.pingju[pingju.name] = 0;
    this._result.pingju[pingju.name]++;
}

url(l) {
    let id = l != null ? this._game.player_id[l] : '';
    return `${this._id}#/${id}/${this._ju}`;
}

analyze_file(filename) {

    let id, paipu;
    if (filename.match(/\.json\.gz$/)) {
        id = filename.replace(/^.*\//,'').replace(/\.json\.gz$/,'');
        paipu = JSON.parse(
                        zlib.gunzipSync(
                            fs.readFileSync(path.join(filename))
                        ).toString());
    }
    else if (filename.match(/\.json$/)) {
        id = filename.replace(/^.*\//,'').replace(/\.json$/,'');
        paipu = JSON.parse(fs.readFileSync(path.join(filename)));
    }
    else return;

    this.analyze(id, paipu);

    return this._result;
}

analyze_dir(dirname, n_try) {

    let logs = fs.readdirSync(dirname)
                    .filter(n=>n.match(/\.json(?:\.gz)?$/)).sort();
    if (n_try == null) n_try = logs.length;

    console.log(`[${Math.min(n_try, logs.length)}]`, new Date().toTimeString());

    while (n_try && logs.length) {

        let filename = logs.shift();
        try {
            let rv = this.analyze_file(path.join(dirname, filename));
            if (! rv) continue;
        }
        catch(e) {
            console.error(`${filename}: ${e}`);
        }

        n_try--;
        if (n_try % 10000 == 0 || logs.length == 0) {
            console.log(`[${Math.min(n_try, logs.length)}]`,
                        new Date().toTimeString());
        }
    }
    return this._result;
}

static analyze(filename, n_try) {

    filename = filename || './';
    const analog = new AnaLog();
    if (fs.statSync(filename).isDirectory())
            analog.analyze_dir(filename, n_try);
    else    analog.analyze_file(filename);

    return analog._result;
}

}
<|endoftext|>


<commit_before>/**
 * @license Utilities module for Rangy.
 * A collection of common selection and range-related tasks, using Rangy. The intention is that each method is
 * self-contained and could be copied and pasted elsewhere and still work.
 *
 * Part of Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on Rangy core.
 *
 * Copyright %%build:year%%, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
rangy.createModule("Util", function(api, module) {
    api.requireModules( ["WrappedSelection", "WrappedRange"] );

    var rangeProto = api.rangePrototype;
    var selProto = api.selectionPrototype;

    /**
     * Convenience method to select a range. Any existing selection will be removed.
     */
    rangeProto.select = function() {
        var sel = api.getSelection(this.getDocument());
        sel.setSingleRange(this);
    };

    selProto.pasteText = function(text) {
        this.deleteFromDocument();
        var range = this.getRangeAt(0);
        var textNode = range.getDocument().createTextNode(text);
        range.insertNode(textNode);
        this.setSingleRange(range);
    };

    rangeProto.pasteText = function(text) {
        this.deleteContents();
        var textNode = this.getDocument().createTextNode(text);
        this.insertNode(textNode);
    };

    selProto.pasteHtml = function(html) {
        this.deleteFromDocument();
        var range = this.getRangeAt(0);
        var frag = this.createContextualFragment(html);
        var lastNode = frag.lastChild;
        range.insertNode(frag);
        if (lastNode) {
            range.setStartAfter(lastNode)
        }
        this.setSingleRange(range);
    };

    rangeProto.pasteHtml = function(html) {
        this.deleteContents();
        var frag = this.createContextualFragment(html);
        this.insertNode(frag);
    };

    /**
     * Convenience method to set a range's start and end boundaries. Overloaded as follows:
     * - Two parameters (node, offset) creates a collapsed range at that position
     * - three parameters (node, startOffset, endOffset) creates a range contained with node starting at startOffset
     *   and ending at endOffset
     * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
     *   startNode and ending at endOffset in endNode
     */
    rangeProto.setStartAndEnd = function() {
        var args = arguments;
        this.setStart(args[0], args[1]);
        switch (args.length) {
            case 2:
                this.collapse(true);
                break;
            case 3:
                this.setEnd(args[0], args[2]);
                break;
            case 4:
                this.setEnd(args[2], args[3]);
                break;
        }
    };

    // TODO: simple selection save/restore
});
<commit_msg>Added selection selectNodeContents to util module<commit_after>/**
 * @license Utilities module for Rangy.
 * A collection of common selection and range-related tasks, using Rangy. The intention is that each method is
 * self-contained and could be copied and pasted elsewhere and still work.
 *
 * Part of Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on Rangy core.
 *
 * Copyright %%build:year%%, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
rangy.createModule("Util", function(api, module) {
    api.requireModules( ["WrappedSelection", "WrappedRange"] );

    var rangeProto = api.rangePrototype;
    var selProto = api.selectionPrototype;

    /**
     * Convenience method to select a range. Any existing selection will be removed.
     */
    rangeProto.select = function() {
        var sel = api.getSelection(this.getDocument());
        sel.setSingleRange(this);
    };

    selProto.pasteText = function(text) {
        this.deleteFromDocument();
        var range = this.getRangeAt(0);
        var textNode = range.getDocument().createTextNode(text);
        range.insertNode(textNode);
        this.setSingleRange(range);
    };

    rangeProto.pasteText = function(text) {
        this.deleteContents();
        var textNode = this.getDocument().createTextNode(text);
        this.insertNode(textNode);
    };

    selProto.pasteHtml = function(html) {
        this.deleteFromDocument();
        var range = this.getRangeAt(0);
        var frag = this.createContextualFragment(html);
        var lastNode = frag.lastChild;
        range.insertNode(frag);
        if (lastNode) {
            range.setStartAfter(lastNode)
        }
        this.setSingleRange(range);
    };

    rangeProto.pasteHtml = function(html) {
        this.deleteContents();
        var frag = this.createContextualFragment(html);
        this.insertNode(frag);
    };

    /**
     * Convenience method to set a range's start and end boundaries. Overloaded as follows:
     * - Two parameters (node, offset) creates a collapsed range at that position
     * - three parameters (node, startOffset, endOffset) creates a range contained with node starting at startOffset
     *   and ending at endOffset
     * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
     *   startNode and ending at endOffset in endNode
     */
    rangeProto.setStartAndEnd = function() {
        var args = arguments;
        this.setStart(args[0], args[1]);
        switch (args.length) {
            case 2:
                this.collapse(true);
                break;
            case 3:
                this.setEnd(args[0], args[2]);
                break;
            case 4:
                this.setEnd(args[2], args[3]);
                break;
        }
    };

    selProto.selectNodeContents = function(node) {
        var range = api.createRange(this.win);
        range.selectNodeContents(node);
        this.setSingleRange(range);
    };

    // TODO: simple selection save/restore
});
<|endoftext|>


<commit_before>/*!
 * Redux-form-validation
 * Validators
 * Most of them are taken shamelessly from backbone.validation (https://github.com/thedersen/backbone.validation)
 */
import {isNumber, Deferred, objectValueToArray} from './validate-utils';
import defaultPatterns from './validate-default-patterns';

export default {
  required(value, rule, list, type) {
    if(type === "[object RadioNodeList]"){
      const radioElements = objectValueToArray(list);
      const checkedInputs = radioElements.filter( item => item.checked === true);
      console.log(checkedInputs.length  ? true : false)
      return checkedInputs.length ? true : false;
    }
    return value ? true : false;
  },
  // function validator
  // Call a function that must return true
  func(value, rule) {
    if (typeof rule !== 'function') {
      console.warn('func validation rule is not a function', rule);
      return false;
    }
    return rule(value);
  },
  // Async validator
  // Send a promess to the model async validation option,
  // the promess must be resolved or rejected by the async function
  async(value, rule) {
    const asyncPromise = new Deferred();
    if (typeof rule.then === 'function') {
      console.warn('async is not a promise', rule);
      asyncPromise.reject();
    }
    rule.call(asyncPromise, value);
    return asyncPromise;
  },
  // Acceptance validator
  // Validates that something has to be accepted, e.g. terms of use
  // true or 'true' are valid
  acceptance(value) {
    if (value != true) {
      return false;
    }
    return true;
  },
  // Acceptance validator
  // Validates that something has to be accepted, e.g. terms of use
  // true or 'true' are valid
  acceptance(value) {
    if (value != true) {
      return false;
    }
    return true;
  },
  // minChecked validator
  // Validate that the checkbox group has a minimum of box checked
  // the min value specified
  minChecked(value, rule, list) {
    const checkboxElements = objectValueToArray(list);
    const checkedInputs = checkboxElements.filter( item => item.checked === true);
    if (checkedInputs.length < rule) {
      return false;
    }
    return true;
  },
  // maxChecked validator
  // Validate that the checkbox group has no more than the maximum allowed of box checked
  // the max value specified
  maxChecked(value, rule, list) {
    const checkboxElements = objectValueToArray(list);
    const checkedInputs = checkboxElements.filter( item => item.checked === true);
    if (checkedInputs.length > rule) {
      return false;
    }
    return true;
  },
  // Max validator
  // Validates that the value has to be a number and equal to or less than
  // the max value specified
  max(value, rule) {
    if (!isNumber(value) || value > rule) {
      return false;
    }
    return true;
  },
  // Range validator
  // Validates that the value has to be a number and equal to or between
  // the two numbers specified
  range(value, rule) {
    if (!isNumber(value) || value < rule[0] || value > rule[1]) {
      return false;
    }
    return true;
  },
  // Length validator
  // Validates that the value has to be a string with length equal to
  // the length value specified
  length(value, rule) {
    if (typeof value !== 'string' || value.length !== rule) {
      return false;
    }
    return true;
  },
  // Min length validator
  // Validates that the value has to be a string with length equal to or greater than
  // the min length value specified
  minLength(value, rule) {
    if (typeof value !== 'string' || value.length < rule) {
      return false;
    }
    return true;
  },
  // Max length validator
  // Validates that the value has to be a string with length equal to or less than
  // the max length value specified
  maxLength(value, rule) {
    if (typeof value !== 'string' || value.length > rule) {
      return false;
    }
    return true;
  },
  // Range length validator
  // Validates that the value has to be a string and equal to or between
  // the two numbers specified
  rangeLength(value, rule) {
    if (typeof value !== 'string' || value.length < rule[0] || value.length > rule[1]) {
      return false;
    }
    return true;
  },
  // One of validator
  // Validates that the value has to be equal to one of the elements in
  // the specified array. Case sensitive matching
  oneOf(value, rule) {
    const elem = rule.find( (arrayVal) => arrayVal == value);
    if (!elem) {
      return false;
    }
    return true;
  },

  // Equal to input in form
  // Validates that the value has to be equal to the value of the attribute
  // with the name specified
  equalTo(value, rule, list, inputType, form) {
    if (value !== form[rule].value) {
      return false;
    }
    return true;
  },
  // Pattern validator
  // Validates that the value has to match the pattern specified.
  // Can be a regular expression or the name of one of the built in patterns
  pattern(value, rule) {
    if (!value || !value.toString().match(defaultPatterns[rule] || rule)) {
      return false;
    }
    return true;
  },
};
<commit_msg>Dedup acceptance function<commit_after>/*!
 * Redux-form-validation
 * Validators
 * Most of them are taken shamelessly from backbone.validation (https://github.com/thedersen/backbone.validation)
 */
import {isNumber, Deferred, objectValueToArray} from './validate-utils';
import defaultPatterns from './validate-default-patterns';

export default {
  required(value, rule, list, type) {
    if(type === "[object RadioNodeList]"){
      const radioElements = objectValueToArray(list);
      const checkedInputs = radioElements.filter( item => item.checked === true);
      console.log(checkedInputs.length  ? true : false)
      return checkedInputs.length ? true : false;
    }
    return value ? true : false;
  },
  // function validator
  // Call a function that must return true
  func(value, rule) {
    if (typeof rule !== 'function') {
      console.warn('func validation rule is not a function', rule);
      return false;
    }
    return rule(value);
  },
  // Async validator
  // Send a promess to the model async validation option,
  // the promess must be resolved or rejected by the async function
  async(value, rule) {
    const asyncPromise = new Deferred();
    if (typeof rule.then === 'function') {
      console.warn('async is not a promise', rule);
      asyncPromise.reject();
    }
    rule.call(asyncPromise, value);
    return asyncPromise;
  },
  // Acceptance validator
  // Validates that something has to be accepted, e.g. terms of use
  // true or 'true' are valid
  acceptance(value) {
    if (value != true) {
      return false;
    }
    return true;
  },
  // minChecked validator
  // Validate that the checkbox group has a minimum of box checked
  // the min value specified
  minChecked(value, rule, list) {
    const checkboxElements = objectValueToArray(list);
    const checkedInputs = checkboxElements.filter( item => item.checked === true);
    if (checkedInputs.length < rule) {
      return false;
    }
    return true;
  },
  // maxChecked validator
  // Validate that the checkbox group has no more than the maximum allowed of box checked
  // the max value specified
  maxChecked(value, rule, list) {
    const checkboxElements = objectValueToArray(list);
    const checkedInputs = checkboxElements.filter( item => item.checked === true);
    if (checkedInputs.length > rule) {
      return false;
    }
    return true;
  },
  // Max validator
  // Validates that the value has to be a number and equal to or less than
  // the max value specified
  max(value, rule) {
    if (!isNumber(value) || value > rule) {
      return false;
    }
    return true;
  },
  // Range validator
  // Validates that the value has to be a number and equal to or between
  // the two numbers specified
  range(value, rule) {
    if (!isNumber(value) || value < rule[0] || value > rule[1]) {
      return false;
    }
    return true;
  },
  // Length validator
  // Validates that the value has to be a string with length equal to
  // the length value specified
  length(value, rule) {
    if (typeof value !== 'string' || value.length !== rule) {
      return false;
    }
    return true;
  },
  // Min length validator
  // Validates that the value has to be a string with length equal to or greater than
  // the min length value specified
  minLength(value, rule) {
    if (typeof value !== 'string' || value.length < rule) {
      return false;
    }
    return true;
  },
  // Max length validator
  // Validates that the value has to be a string with length equal to or less than
  // the max length value specified
  maxLength(value, rule) {
    if (typeof value !== 'string' || value.length > rule) {
      return false;
    }
    return true;
  },
  // Range length validator
  // Validates that the value has to be a string and equal to or between
  // the two numbers specified
  rangeLength(value, rule) {
    if (typeof value !== 'string' || value.length < rule[0] || value.length > rule[1]) {
      return false;
    }
    return true;
  },
  // One of validator
  // Validates that the value has to be equal to one of the elements in
  // the specified array. Case sensitive matching
  oneOf(value, rule) {
    const elem = rule.find( (arrayVal) => arrayVal == value);
    if (!elem) {
      return false;
    }
    return true;
  },

  // Equal to input in form
  // Validates that the value has to be equal to the value of the attribute
  // with the name specified
  equalTo(value, rule, list, inputType, form) {
    if (value !== form[rule].value) {
      return false;
    }
    return true;
  },
  // Pattern validator
  // Validates that the value has to match the pattern specified.
  // Can be a regular expression or the name of one of the built in patterns
  pattern(value, rule) {
    if (!value || !value.toString().match(defaultPatterns[rule] || rule)) {
      return false;
    }
    return true;
  },
};
<|endoftext|>


<commit_before>/*

 Copyright (c) 2017 Jirtdan Team and other collaborators

 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.

 */

"use strict";
/*jshint esversion: 6*/

import {
    BaseControl,
    DEFAULT_STROKE_COLOR,
    DEFAULT_STROKE_WIDTH,
    DEFAULT_FILL_COLOR,
    DEBUG
} from '../BaseControl.js'
import {ConnectionPin} from '../ConnectionPin.js'

const LOGTAG = "OrGate";

/**
 * Control for OR gate.
 *
 * Truth table:
 * |In1|In2|Out|
 * |0  |0  |0  |
 * |0  |1  |1  |
 * |1  |0  |1  |
 * |1  |1  |1  |
 */
export class OrGate extends BaseControl {
    /**
     * Constructor for OrGate
     * @param paper raphael paper object.
     */
    constructor(paper) {
        super(paper);
        this.inputPin1Value = 0;
        this.inputPin2Value = 0;
    }

    /**
     * Draw the component surface
     */
    draw() {
        super.draw();

        this.componentInputWire1 = this.paper.path("m 10,11.233446 19.55419,0");
        this.componentInputWire1.attr("stroke", DEFAULT_STROKE_COLOR);
        this.componentInputWire1.attr("stroke-width", DEFAULT_STROKE_WIDTH);

        this.componentInputWire2 = this.paper.path("m 10,38.765748 19.55419,0");
        this.componentInputWire2.attr("stroke", DEFAULT_STROKE_COLOR);
        this.componentInputWire2.attr("stroke-width", DEFAULT_STROKE_WIDTH);

        this.componentOutputWire = this.paper.path("m 76.907772,24.999597 19.554188,0");
        this.componentOutputWire.attr("stroke", DEFAULT_STROKE_COLOR);
        this.componentOutputWire.attr("stroke-width", DEFAULT_STROKE_WIDTH);

        this.componentBodyShape = this.paper.path("M 23.884294,0.98165499 A 6.6318398,24.254708 " +
            "0 0 1 29.647279,24.999109 6.6318398,24.254708 0 0 1 23.93612,48.992974 " +
            "l 13.940289,0 c 2.854669,-0.09472 5.720639,0.223753 8.564575,-0.230024 " +
            "0.152743,-0.01531 0.307035,-0.04378 0.460694,-0.06684 A 35.091661,36.015432 " +
            "0 0 0 77.284394,24.983385 35.091661,36.015432 0 0 0 46.626701,1.2922936 " +
            "C 45.767476,1.1103691 44.909376,1.0045184 44.062656,0.98165499 c " +
            "-6.725618,-2.3055e-4 -13.452632,3.721e-5 -20.178362,0 z");
        this.componentBodyShape.attr("stroke", DEFAULT_STROKE_COLOR);
        this.componentBodyShape.attr("stroke-width", DEFAULT_STROKE_WIDTH);
        this.componentBodyShape.attr("stroke-linejoin", "round");
        this.componentBodyShape.attr("stroke-miterlimit", 4);

        this.componentBodyFill = this.paper.path("m 26.192665,2.2770733 c 5.926298,-2.02e-5 11.853246,-2.014e-4 " +
            "17.779164,0 0.808697,0.021743 1.631486,0.1223453 2.457821,0.2968739 a 0.84220914,1.1473132 " +
            "0 0 0 0.08659,0.011777 C 60.013606,3.5107633 71.619229,12.461416 76.316088,25.132568 " +
            "71.665013,37.726207 60.187004,46.666381 46.786135,47.699048 a 0.84220914,1.1473132 0 " +
            "0 0 -0.04471,0.004 c -0.168086,0.02516 -0.311764,0.05104 -0.43009,0.06292 a 0.84220914,1.1473132 " +
            "0 0 0 -0.03754,0.006 c -2.741088,0.436292 -5.5866,0.125475 -8.507843,0.222173 l -11.557392,0 " +
            "c 1.014483,-1.619407 1.840575,-3.848116 2.495352,-6.592178 1.056825,-4.429 1.67379,-10.107999 " +
            "1.675592,-16.255431 -0.0022,-6.183935 -0.62538,-11.88817 -1.692919,-16.3261906 -0.655198,-2.723828 " +
            "-1.482205,-4.9369847 -2.493904,-6.5430658 z");
        this.componentBodyFill.attr("stroke-width", 1);
        this.componentBodyFill.attr("stroke", DEFAULT_FILL_COLOR);
        this.componentBodyFill.attr("fill", DEFAULT_FILL_COLOR);

        this.componentBodyGradient = this.paper.path("m 26.192665,1.9187 c 1.014674,1.606081 1.844114,3.8192379 " +
            "2.501239,6.5430657 0.991662,4.1104943 1.594219,9.3135613 1.680522,14.9715933 2.199007,-1.148891 " +
            "4.576339,-2.065092 7.204085,-2.520499 3.49611,-0.413432 7.164302,0.669677 9.326083,2.819336 " +
            "2.184037,1.721099 3.994268,3.96551 7.050663,4.836523 2.638613,0.438497 5.399503,0.24912 " +
            "8.065335,0.03343 4.898971,-0.632573 9.64962,-1.772778 14.217111,-3.24991 0.07526,-0.192658 " +
            "0.153792,-0.383594 0.225806,-0.578015 C 71.752836,12.103062 60.113075,3.1524102 " +
            "46.576016,2.2273717 a 0.84468635,1.1473131 0 0 1 -0.08684,-0.011777 C 45.660408,2.0410655 " +
            "44.83519,1.9404633 44.024116,1.9187202 c -5.94334,-2.014e-4 -11.887721,-2.02e-5 -17.831451,0 z");
        this.componentBodyGradient.attr("stroke-width", 0);
        this.componentBodyGradient.attr("fill", "90-#0066ff-#fff");
    }

    /**
     * Initialize the control.
     */
    initControl() {
        super.initControl();
        const _this = this;

        const inputPin1 = new ConnectionPin(this, 5, 11, "in");
        const inputPin2 = new ConnectionPin(this, 5, 39, "in");
        const outputPin = new ConnectionPin(this, 102, 25, "out");

        inputPin1.addStateChangeListener(
            function (newState) {
                _this.inputPin1Value = newState;
                outputPin.notifyStateChange(_this.getValue());

            });

        inputPin2.addStateChangeListener(
            function (newState) {
                _this.inputPin2Value = newState;
                outputPin.notifyStateChange(_this.getValue());
            });

        this.setShapes([
            this.componentBodyShape,
            this.componentBodyFill,
            this.componentBodyGradient,
            this.componentInputWire1,
            this.componentInputWire2,
            this.componentOutputWire]);
        this.addInputPins(inputPin1, inputPin2);
        this.addOutputPins(outputPin);
    }

    /**
     * An event fired when element is selected.
     * @param event
     */
    onSelect(event) {
        super.onSelect(event);
        this.glow = this.componentBodyShape.glow();
        this.glow.toBack();
    }

    /**
     * Get the calculated value of the expression.
     * @returns {number|*}
     */
    getValue() {
        return this.inputPin1Value || this.inputPin2Value;
    }
}
<commit_msg>FIx lint error in OrGate<commit_after>/*

 Copyright (c) 2017 Jirtdan Team and other collaborators

 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.

 */

'use strict';
/* jshint esversion: 6*/

import {
    BaseControl,
    DEFAULT_STROKE_COLOR,
    DEFAULT_STROKE_WIDTH,
    DEFAULT_FILL_COLOR,
    DEBUG,
} from '../BaseControl.js';
import {ConnectionPin} from '../ConnectionPin.js';

const LOGTAG = 'OrGate';

/**
 * Control for OR gate.
 *
 * Truth table:
 * |In1|In2|Out|
 * |0  |0  |0  |
 * |0  |1  |1  |
 * |1  |0  |1  |
 * |1  |1  |1  |
 */
export class OrGate extends BaseControl {
    /**
     * Constructor for OrGate
     * @param paper raphael paper object.
     */
    constructor(paper) {
        super(paper);
        this.inputPin1Value = 0;
        this.inputPin2Value = 0;
    }

    /**
     * Draw the component surface
     */
    draw() {
        super.draw();

        this.componentInputWire1 = this.paper.path('m 10,11.233446 19.55419,0');
        this.componentInputWire1.attr('stroke', DEFAULT_STROKE_COLOR);
        this.componentInputWire1.attr('stroke-width', DEFAULT_STROKE_WIDTH);

        this.componentInputWire2 = this.paper.path('m 10,38.765748 19.55419,0');
        this.componentInputWire2.attr('stroke', DEFAULT_STROKE_COLOR);
        this.componentInputWire2.attr('stroke-width', DEFAULT_STROKE_WIDTH);

        this.componentOutputWire = this.paper.path('m 76.907772,24.999597 19.554188,0');
        this.componentOutputWire.attr('stroke', DEFAULT_STROKE_COLOR);
        this.componentOutputWire.attr('stroke-width', DEFAULT_STROKE_WIDTH);

        this.componentBodyShape = this.paper.path('M 23.884294,0.98165499 A 6.6318398,24.254708 ' +
            '0 0 1 29.647279,24.999109 6.6318398,24.254708 0 0 1 23.93612,48.992974 ' +
            'l 13.940289,0 c 2.854669,-0.09472 5.720639,0.223753 8.564575,-0.230024 ' +
            '0.152743,-0.01531 0.307035,-0.04378 0.460694,-0.06684 A 35.091661,36.015432 ' +
            '0 0 0 77.284394,24.983385 35.091661,36.015432 0 0 0 46.626701,1.2922936 ' +
            'C 45.767476,1.1103691 44.909376,1.0045184 44.062656,0.98165499 c ' +
            '-6.725618,-2.3055e-4 -13.452632,3.721e-5 -20.178362,0 z');
        this.componentBodyShape.attr('stroke', DEFAULT_STROKE_COLOR);
        this.componentBodyShape.attr('stroke-width', DEFAULT_STROKE_WIDTH);
        this.componentBodyShape.attr('stroke-linejoin', 'round');
        this.componentBodyShape.attr('stroke-miterlimit', 4);

        this.componentBodyFill = this.paper.path('m 26.192665,2.2770733 c 5.926298,-2.02e-5 11.853246,-2.014e-4 ' +
            '17.779164,0 0.808697,0.021743 1.631486,0.1223453 2.457821,0.2968739 a 0.84220914,1.1473132 ' +
            '0 0 0 0.08659,0.011777 C 60.013606,3.5107633 71.619229,12.461416 76.316088,25.132568 ' +
            '71.665013,37.726207 60.187004,46.666381 46.786135,47.699048 a 0.84220914,1.1473132 0 ' +
            '0 0 -0.04471,0.004 c -0.168086,0.02516 -0.311764,0.05104 -0.43009,0.06292 a 0.84220914,1.1473132 ' +
            '0 0 0 -0.03754,0.006 c -2.741088,0.436292 -5.5866,0.125475 -8.507843,0.222173 l -11.557392,0 ' +
            'c 1.014483,-1.619407 1.840575,-3.848116 2.495352,-6.592178 1.056825,-4.429 1.67379,-10.107999 ' +
            '1.675592,-16.255431 -0.0022,-6.183935 -0.62538,-11.88817 -1.692919,-16.3261906 -0.655198,-2.723828 ' +
            '-1.482205,-4.9369847 -2.493904,-6.5430658 z');
        this.componentBodyFill.attr('stroke-width', 1);
        this.componentBodyFill.attr('stroke', DEFAULT_FILL_COLOR);
        this.componentBodyFill.attr('fill', DEFAULT_FILL_COLOR);

        this.componentBodyGradient = this.paper.path('m 26.192665,1.9187 c 1.014674,1.606081 1.844114,3.8192379 ' +
            '2.501239,6.5430657 0.991662,4.1104943 1.594219,9.3135613 1.680522,14.9715933 2.199007,-1.148891 ' +
            '4.576339,-2.065092 7.204085,-2.520499 3.49611,-0.413432 7.164302,0.669677 9.326083,2.819336 ' +
            '2.184037,1.721099 3.994268,3.96551 7.050663,4.836523 2.638613,0.438497 5.399503,0.24912 ' +
            '8.065335,0.03343 4.898971,-0.632573 9.64962,-1.772778 14.217111,-3.24991 0.07526,-0.192658 ' +
            '0.153792,-0.383594 0.225806,-0.578015 C 71.752836,12.103062 60.113075,3.1524102 ' +
            '46.576016,2.2273717 a 0.84468635,1.1473131 0 0 1 -0.08684,-0.011777 C 45.660408,2.0410655 ' +
            '44.83519,1.9404633 44.024116,1.9187202 c -5.94334,-2.014e-4 -11.887721,-2.02e-5 -17.831451,0 z');
        this.componentBodyGradient.attr('stroke-width', 0);
        this.componentBodyGradient.attr('fill', '90-#0066ff-#fff');
    }

    /**
     * Initialize the control.
     */
    initControl() {
        super.initControl();
        const _this = this;

        const inputPin1 = new ConnectionPin(this, 5, 11, 'in');
        const inputPin2 = new ConnectionPin(this, 5, 39, 'in');
        const outputPin = new ConnectionPin(this, 102, 25, 'out');

        inputPin1.addStateChangeListener(
            function(newState) {
                _this.inputPin1Value = newState;
                outputPin.notifyStateChange(_this.getValue());
            });

        inputPin2.addStateChangeListener(
            function(newState) {
                _this.inputPin2Value = newState;
                outputPin.notifyStateChange(_this.getValue());
            });

        this.setShapes([
            this.componentBodyShape,
            this.componentBodyFill,
            this.componentBodyGradient,
            this.componentInputWire1,
            this.componentInputWire2,
            this.componentOutputWire]);
        this.addInputPins(inputPin1, inputPin2);
        this.addOutputPins(outputPin);
    }

    /**
     * An event fired when element is selected.
     * @param event
     */
    onSelect(event) {
        super.onSelect(event);
        this.glow = this.componentBodyShape.glow();
        this.glow.toBack();
    }

    /**
     * Get the calculated value of the expression.
     * @returns {number|*}
     */
    getValue() {
        return this.inputPin1Value || this.inputPin2Value;
    }
}
<|endoftext|>


<commit_before>"use strict";
/*jshint esversion: 6*/

class OrGate extends BaseControl {
    /**
     * Constructor for AndGate
     * @param paper raphael paper object.
     */
    constructor(paper) {
        super(paper);
        this.pin1Value = 0;
        this.pin2Value = 0;
    }

    initControl() {
        super.initControl();

        let componentBody = this.paper.path("m 101.07632,3.5678062 7.66487,8.9876198 7.62023,8.9375 c 0.0373,0.0444 20.25609,24.17656 20.25609,60.90816 0,36.765454 -20.27841,60.936804 -20.27841,60.936804 l -15.26278,17.89647 24.11086,0 53.34156,0 c 9.13623,0 30.35123,0.12027 54.07828,-9.23108 23.7379,-9.35571 50.05011,-28.36946 70.25634,-64.352854 1.47897,-2.14502 2.31316,-4.63806 2.70129,-5.68619 l -2.70129,-4.8125 C 262.42079,5.1313062 196.82451,3.5678062 178.52874,3.5678062 l -53.34156,0 z M 306.49478,82.401136 c 76.20221,0 95.25276,0 95.25276,0 M 131.373,119.06778 l -102.873,0 m 99.06288,-73.333284 -99.06288,0");
        let componentPin1 = new ConnectionPin(this, 15, 23, "in");
        let componentPin2 = new ConnectionPin(this, 15, 143, "in");
        let componentPin3 = new ConnectionPin(this, 415, 83, "out");
        let _this = this;
        componentPin1.addStateChangeListener(
            function (newState) {
                _this.pin1Value = newState;
                componentPin3.notifyStateChange(_this.getValue());

            });

        componentPin2.addStateChangeListener(
            function (newState) {
                _this.pin2Value = newState;
                componentPin3.notifyStateChange(_this.getValue());
            });

        this.setShapes([componentBody]);
        this.addInputPins(componentPin1, componentPin2);
        this.addOutputPins(componentPin3);
    }

    getValue() {
        return this.pin1Value || this.pin2Value;
    }
}
<commit_msg>[Init] Fix the pin circle coordinates in OrGate<commit_after>"use strict";
/*jshint esversion: 6*/

class OrGate extends BaseControl {
    /**
     * Constructor for AndGate
     * @param paper raphael paper object.
     */
    constructor(paper) {
        super(paper);
        this.pin1Value = 0;
        this.pin2Value = 0;
    }

    initControl() {
        super.initControl();

        let componentBody = this.paper.path("m 101.07632,3.5678062 7.66487,8.9876198 7.62023,8.9375 c 0.0373,0.0444 20.25609,24.17656 20.25609,60.90816 0,36.765454 -20.27841,60.936804 -20.27841,60.936804 l -15.26278,17.89647 24.11086,0 53.34156,0 c 9.13623,0 30.35123,0.12027 54.07828,-9.23108 23.7379,-9.35571 50.05011,-28.36946 70.25634,-64.352854 1.47897,-2.14502 2.31316,-4.63806 2.70129,-5.68619 l -2.70129,-4.8125 C 262.42079,5.1313062 196.82451,3.5678062 178.52874,3.5678062 l -53.34156,0 z M 306.49478,82.401136 c 76.20221,0 95.25276,0 95.25276,0 M 131.373,119.06778 l -102.873,0 m 99.06288,-73.333284 -99.06288,0");
        let componentPin1 = new ConnectionPin(this, 15, 48, "in");
        let componentPin2 = new ConnectionPin(this, 15, 118, "in");
        let componentPin3 = new ConnectionPin(this, 415, 83, "out");
        let _this = this;
        componentPin1.addStateChangeListener(
            function (newState) {
                _this.pin1Value = newState;
                componentPin3.notifyStateChange(_this.getValue());

            });

        componentPin2.addStateChangeListener(
            function (newState) {
                _this.pin2Value = newState;
                componentPin3.notifyStateChange(_this.getValue());
            });

        this.setShapes([componentBody]);
        this.addInputPins(componentPin1, componentPin2);
        this.addOutputPins(componentPin3);
    }

    getValue() {
        return this.pin1Value || this.pin2Value;
    }
}
<|endoftext|>


<commit_before>// Application specific behaviour
function addAlternateTableBehaviour() {
  $("table.list tr:odd").addClass("odd");
}

function addNestedFormBehaviour() {
  $('body').on('click', '.delete-nested-form-item', function(event) {
    var item = $(this).parents('.nested-form-item');
    // Hide item
    item.hide();
    // Mark as ready to delete
    item.find("input[name$='[_destroy]']").val("1");
    item.addClass('delete');
    // Drop input fields to prevent browser validation problems
    item.find(":input").not("[name$='[_destroy]'], [name$='[id]']").remove();

    // TODO: should be callbacks
    updatePositions($(this).parents('.nested-form-container'));
    updateLineItems();

    // Don't follow link
    event.preventDefault();
  });
}

// Currency helpers
function currencyRound(value) {
  if (isNaN(value)) {
    return 0.0;
  };

  rounded = Math.round(value * 20) / 20;

  return rounded.toFixed(2);
}

// Line Item calculation
function updateLineItemPrice(lineItem) {
  var list = lineItem.parent();
  var reference_code = lineItem.find(":input[name$='[reference_code]']").val();
  var quantity = lineItem.find(":input[name$='[quantity]']").val();
  var after_saldo_line_item = lineItem.prev().hasClass('saldo_line_item')
  var is_saldo_line_item = lineItem.hasClass('saldo_line_item')
  if ((quantity == '%' || quantity == 'saldo_of') && (after_saldo_line_item || is_saldo_line_item)) {
    var included_items;
    if (reference_code == '') {
      included_items = lineItem.prevAll('.line_item');
    } else {
      // Should match using ~= but acts_as_taggable_adds_colons between tags
      included_items = list.find(":input[name$='[code]'][value='" + reference_code + "']").parents('.line_item, .saldo_line_item');
      if (included_items.length == 0) {
        // Should match using ~= but acts_as_taggable_adds_colons between tags
        included_items = list.find(":input[name$='[include_in_saldo_list]'][value*='" + reference_code + "']").parents('.line_item, .saldo_line_item');
      }
    }
    var price_input = lineItem.find(":input[name$='[price]']");
    price_input.val(calculateTotalAmount(included_items));
  }
}

function updateAllLineItemPrices() {
  $('.line_item, .saldo_line_item').each(function() {
    updateLineItemPrice($(this));
  });
}

function calculateLineItemTotalAmount(lineItem) {
  var times_input = lineItem.find(":input[name$='[times]']");
  var times = accounting.parse(times_input.val());
  if (isNaN(times)) {
    times = 1;
  };

  var quantity_input = lineItem.find(":input[name$='[quantity]']");
  var price_input = lineItem.find(":input[name$='[price]']");
  var price = accounting.parse(price_input.val());

  // For 'saldo_of' items, we don't take accounts into account
  if (quantity_input.val() == "saldo_of") {
    return currencyRound(price);
  };

  var direct_account_id = $('#line_items').data('direct-account-id');
  var direct_account_factor = $('#line_items').data('direct-account-factor');

  var factor = 0;
  if (lineItem.find(":input[name$='[debit_account_id]']").val() == direct_account_id) {
    factor = 1;
  };
  if (lineItem.find(":input[name$='[credit_account_id]']").val() == direct_account_id) {
    factor = -1;
  };

  if (quantity_input.val() == '%') {
    times = times / 100;
  };

  return currencyRound(times * price * factor * direct_account_factor);
}

function updateLineItemTotalAmount(lineItem) {
  var total_amount_input = lineItem.find(".total_amount");
  var total_amount = accounting.formatNumber(calculateLineItemTotalAmount(lineItem));

  // Update Element
  total_amount_input.text(total_amount);
}

function calculateTotalAmount(lineItems) {
  var total_amount = 0;
  $(lineItems).each(function() {
    total_amount += accounting.parse($(this).find(".total_amount").text());
  });

  return currencyRound(total_amount);
}

function updateLineItems() {
  if ($('#line_items').length > 0) {
    $('.line_item, .saldo_line_item').each(function() {
      updateLineItemPrice($(this));
      updateLineItemTotalAmount($(this));
    });
  };
}

// Recalculate after every key stroke
function handleLineItemChange(event) {
  // If character is <return>
  if(event.keyCode == 13) {
    // ...trigger form action
    $(event.currentTarget).submit();
  } else if(event.keyCode == 32) {
    // ...trigger form action
    $(event.currentTarget).submit();
  } else {
    updateLineItems();
  }
}

function addCalculateTotalAmountBehaviour() {
  $("#line_items").find(":input[name$='[times]'], :input[name$='[quantity]'], :input[name$='[price]'], input[name$='[reference_code]']").on('keyup', handleLineItemChange);
  $("#line_items").bind("sortstop", handleLineItemChange);
}

// Sorting
function updatePositions(collection) {
  var items = collection.find('.nested-form-item').not('.delete');
  items.each(function(index, element) {
    $(this).find("input[id$='_position']").val(index + 1)
  });
}

// Override generic version in cyt.js
function addSortableBehaviour() {
  $(".sortable").sortable({
    placeholder: "ui-state-highlight",
    forcePlaceholderSize: true,
    stop:        function(event, ui) {
      updatePositions($(this));
    }
  });

  $('.sortable').each(function() {
    updatePositions($(this));
  });
}

function initAccounting() {
  // accounting.js
  // Settings object that controls default parameters for library methods:
  accounting.settings = {
          currency: {
                  symbol : "",   // default currency symbol is '$'
                  format: "%v", // controls output: %s = symbol, %v = value/number (can be object: see below)
                  decimal : ".",  // decimal point separator
                  thousand: "'",  // thousands separator
                  precision : 2   // decimal places
          },
          number: {
                  precision : 2,  // default precision on numbers is 0
                  thousand: "'",
                  decimal : "."
          }
  }
}

// Initialize behaviours
function initializeBehaviours() {
  // Init settings
  initAccounting();

  // from cyt.js
  addComboboxBehaviour();
  addAutofocusBehaviour();
  addDatePickerBehaviour();
  addSortableBehaviour();
  addLinkifyContainersBehaviour();
  addIconTooltipBehaviour();
  addModalBehaviour();

  // application
  addAlternateTableBehaviour();
  addNestedFormBehaviour();

  addCalculateTotalAmountBehaviour();

  updateLineItems();

  // twitter bootstrap
  $(function () {
    $('.tabs').tabs();
    $(".alert").alert();
    $("*[rel=popover]").popover({
      offset: 10
    });
    $('.small-tooltip').tooltip({
      placement: 'right'
    });
  })

  // select2
  $('.select2').select2({
      allowClear: true
  });
  $('.select2-tags').each(function(index, element) {
    var tags = $(element).data('tags') || '';

    $(element).select2({
      tags: tags,
      tokenSeparators: [","]
    })
  })

  // best_in_place
  $(".best_in_place").best_in_place();
}

// Loads functions after DOM is ready
$(document).ready(initializeBehaviours);
<commit_msg>Revert "disable auto price calculation of line items w/o saldo line item"<commit_after>// Application specific behaviour
function addAlternateTableBehaviour() {
  $("table.list tr:odd").addClass("odd");
}

function addNestedFormBehaviour() {
  $('body').on('click', '.delete-nested-form-item', function(event) {
    var item = $(this).parents('.nested-form-item');
    // Hide item
    item.hide();
    // Mark as ready to delete
    item.find("input[name$='[_destroy]']").val("1");
    item.addClass('delete');
    // Drop input fields to prevent browser validation problems
    item.find(":input").not("[name$='[_destroy]'], [name$='[id]']").remove();

    // TODO: should be callbacks
    updatePositions($(this).parents('.nested-form-container'));
    updateLineItems();

    // Don't follow link
    event.preventDefault();
  });
}

// Currency helpers
function currencyRound(value) {
  if (isNaN(value)) {
    return 0.0;
  };

  rounded = Math.round(value * 20) / 20;

  return rounded.toFixed(2);
}

// Line Item calculation
function updateLineItemPrice(lineItem) {
  var list = lineItem.parent();
  var reference_code = lineItem.find(":input[name$='[reference_code]']").val();
  var quantity = lineItem.find(":input[name$='[quantity]']").val();
  if (quantity == '%' || quantity == 'saldo_of') {
    var included_items;
    if (reference_code == '') {
      included_items = lineItem.prevAll('.line_item');
    } else {
      // Should match using ~= but acts_as_taggable_adds_colons between tags
      included_items = list.find(":input[name$='[code]'][value='" + reference_code + "']").parents('.line_item, .saldo_line_item');
      if (included_items.length == 0) {
        // Should match using ~= but acts_as_taggable_adds_colons between tags
        included_items = list.find(":input[name$='[include_in_saldo_list]'][value*='" + reference_code + "']").parents('.line_item, .saldo_line_item');
      }
    }
    var price_input = lineItem.find(":input[name$='[price]']");
    price_input.val(calculateTotalAmount(included_items));
  }
}

function updateAllLineItemPrices() {
  $('.line_item, .saldo_line_item').each(function() {
    updateLineItemPrice($(this));
  });
}

function calculateLineItemTotalAmount(lineItem) {
  var times_input = lineItem.find(":input[name$='[times]']");
  var times = accounting.parse(times_input.val());
  if (isNaN(times)) {
    times = 1;
  };

  var quantity_input = lineItem.find(":input[name$='[quantity]']");
  var price_input = lineItem.find(":input[name$='[price]']");
  var price = accounting.parse(price_input.val());

  // For 'saldo_of' items, we don't take accounts into account
  if (quantity_input.val() == "saldo_of") {
    return currencyRound(price);
  };

  var direct_account_id = $('#line_items').data('direct-account-id');
  var direct_account_factor = $('#line_items').data('direct-account-factor');

  var factor = 0;
  if (lineItem.find(":input[name$='[debit_account_id]']").val() == direct_account_id) {
    factor = 1;
  };
  if (lineItem.find(":input[name$='[credit_account_id]']").val() == direct_account_id) {
    factor = -1;
  };

  if (quantity_input.val() == '%') {
    times = times / 100;
  };

  return currencyRound(times * price * factor * direct_account_factor);
}

function updateLineItemTotalAmount(lineItem) {
  var total_amount_input = lineItem.find(".total_amount");
  var total_amount = accounting.formatNumber(calculateLineItemTotalAmount(lineItem));

  // Update Element
  total_amount_input.text(total_amount);
}

function calculateTotalAmount(lineItems) {
  var total_amount = 0;
  $(lineItems).each(function() {
    total_amount += accounting.parse($(this).find(".total_amount").text());
  });

  return currencyRound(total_amount);
}

function updateLineItems() {
  if ($('#line_items').length > 0) {
    $('.line_item, .saldo_line_item').each(function() {
      updateLineItemPrice($(this));
      updateLineItemTotalAmount($(this));
    });
  };
}

// Recalculate after every key stroke
function handleLineItemChange(event) {
  // If character is <return>
  if(event.keyCode == 13) {
    // ...trigger form action
    $(event.currentTarget).submit();
  } else if(event.keyCode == 32) {
    // ...trigger form action
    $(event.currentTarget).submit();
  } else {
    updateLineItems();
  }
}

function addCalculateTotalAmountBehaviour() {
  $("#line_items").find(":input[name$='[times]'], :input[name$='[quantity]'], :input[name$='[price]'], input[name$='[reference_code]']").on('keyup', handleLineItemChange);
  $("#line_items").bind("sortstop", handleLineItemChange);
}

// Sorting
function updatePositions(collection) {
  var items = collection.find('.nested-form-item').not('.delete');
  items.each(function(index, element) {
    $(this).find("input[id$='_position']").val(index + 1)
  });
}

// Override generic version in cyt.js
function addSortableBehaviour() {
  $(".sortable").sortable({
    placeholder: "ui-state-highlight",
    forcePlaceholderSize: true,
    stop:        function(event, ui) {
      updatePositions($(this));
    }
  });

  $('.sortable').each(function() {
    updatePositions($(this));
  });
}

function initAccounting() {
  // accounting.js
  // Settings object that controls default parameters for library methods:
  accounting.settings = {
          currency: {
                  symbol : "",   // default currency symbol is '$'
                  format: "%v", // controls output: %s = symbol, %v = value/number (can be object: see below)
                  decimal : ".",  // decimal point separator
                  thousand: "'",  // thousands separator
                  precision : 2   // decimal places
          },
          number: {
                  precision : 2,  // default precision on numbers is 0
                  thousand: "'",
                  decimal : "."
          }
  }
}

// Initialize behaviours
function initializeBehaviours() {
  // Init settings
  initAccounting();

  // from cyt.js
  addComboboxBehaviour();
  addAutofocusBehaviour();
  addDatePickerBehaviour();
  addSortableBehaviour();
  addLinkifyContainersBehaviour();
  addIconTooltipBehaviour();
  addModalBehaviour();

  // application
  addAlternateTableBehaviour();
  addNestedFormBehaviour();

  addCalculateTotalAmountBehaviour();

  updateLineItems();

  // twitter bootstrap
  $(function () {
    $('.tabs').tabs();
    $(".alert").alert();
    $("*[rel=popover]").popover({
      offset: 10
    });
    $('.small-tooltip').tooltip({
      placement: 'right'
    });
  })

  // select2
  $('.select2').select2({
      allowClear: true
  });
  $('.select2-tags').each(function(index, element) {
    var tags = $(element).data('tags') || '';

    $(element).select2({
      tags: tags,
      tokenSeparators: [","]
    })
  })

  // best_in_place
  $(".best_in_place").best_in_place();
}

// Loads functions after DOM is ready
$(document).ready(initializeBehaviours);
<|endoftext|>


<commit_before>var OCA = function($){
  // CONSTANTS / GLOBAL VARIABLES
  const FILES_API = '/api/files/';
  const KNOWN_FILE_EXTENSIONS = {
    as3:  'as3',
    sh:   'bash',
    cf:   'coldfusion',
    cs:   'csharp',
    cpp:  'cpp',
    h:    'cpp',
    hpp:  'cpp',
    c:    'cpp',
    cc:   'cpp',
    cxx:  'cpp',
    hxx:  'cpp',
    css:  'css',
    scss: 'css',
    dpk:  'delphi',
    diff: 'diff',
    patch:'patch',
    erl:  'erlang',
    gy:   'groovy',
    gsh:  'groovy',
    gvy:  'groovy',
    groovy:'groovy',
    js:   'javascript',
    json: 'javascript',
    java: 'java',
    jfx:  'javafx',
    pl:   'perl',
    php:  'php',
    txt:  'text',
    ps:   'powershell',
    py:   'python',
    rb:   'ruby',
    erb:  'ruby',
    scala:'scala',
    sql:  'sql',
    vb:   'vb',
    vbnet:'vb',
    xml:  'xml',
    html: 'xml',
    xhtml:'xml'
  };

  // Lifted from Mustache
  // (https://github.com/janl/mustache.js/blob/master/mustache.js)
  const ENTITY_MAP = {
    "&": "&",
    "<": "<",
    ">": ">",
    '"': '"',
    "'": ''',
    "/": '/'
  };

  var curFileInfo;

  // FUNCTIONS

  var editComment = function(e){
    var target = $(e.target);
    target.addClass('comment-in-edit');
    if(target.data('timeout')){
      clearTimeout(target.data('timeout'));
    }
    target.data('timeout', setTimeout(function(){
      target.removeClass('comment-in-edit');
      target.parent().find('.comment-saved').show().fadeOut(2000);
    }, 2000));
  };

  var createComment = function(location){
    var comment = $('#comment-template').clone();
    comment.attr('id', '');
    comment.find('.comment-owner').html($('#current-email').html());
    $('#comments').append(comment);
    comment.find('.comment-body').focus();
  };

  // Hides all highlighted selections.
  var hideHighlights = function(){
    $('.code .selected').removeClass('selected');
  };

  var normalizeLocation = function(loc){
    var normLoc = {};
    if(loc.start_line < loc.end_line ||
        (loc.start_line === loc.end_line && loc.start_column < loc.end_column)){
      normLoc.start_line    = loc.start_line;
      normLoc.start_column  = loc.start_column;
      normLoc.end_line      = loc.end_line;
      normLoc.end_column    = loc.end_column;
    } else {
      normLoc.start_line   = loc.end_line;
      normLoc.start_column = loc.end_column+1;
      normLoc.end_line     = loc.start_line;
      normLoc.end_column   = loc.start_column;
    }
    return normLoc;
  };

  // Highlights the given comment location.
  // @param {simple object} loc An object with the fields:
  //    start_line:    The line of code to start on, base 1.
  //    start_column:  The starting column, base 1.
  //    end_line:      The line of code to end on, base 1.
  //    end_column:    The final column to highlight, base 1.
  var highlightSelection = function(loc){
    loc = normalizeLocation(loc);
    var i, j;
    for(i = loc.start_line; i <= loc.end_line; i++){
      var start = (i === loc.start_line) ? loc.start_column : 1;
      var end = (i === loc.end_line) ? loc.end_column :
        $('.content-line'+ i).size();
      console.log('Line '+ i +': '+ start +' to '+ end);
      for(j = start; j <= end; j++){
        $('#'+ i +'_'+ j).addClass('selected');
        // Highlight the endcap if the selection spans to lines below.
        // if(i !== loc.end_line){
        //   $('#'+ i +'_endcap').addClass('selected');
        // }
      }
    }
  };

  // Modifies the displayed file content to make highlighting easier.
  // @param {DOM Elmement} contentElm The element containing the file content.
  //                                  This should already be processed by
  //                                  SyntaxHighlighter.
  var addColumnsToHighlightedCode = function(contentElm){
    $(contentElm).find('.line').each(function(i,lineElm){
      var children = lineElm.childNodes;
      var i, colNo = 1;
      var lineNo = parseInt(lineElm.className.split(/\s+/)[1].substr(6));
      for(i = 0; i < children.length; i++){
        if(children[i].nodeType === 3){
          var j, newHTML = '';
          for(j = 0; j < children[i].nodeValue.length; j++){
            newHTML += ''+
              children[i].nodeValue[j] +'';
            colNo++;
          }
          $(children[i]).replaceWith(newHTML);
        } else {
          var j, newHTML = '';
          for(j = 0; j < children[i].innerText.length; j++){
            newHTML += ''+ children[i].innerText[j] +
              '';
            colNo++;
          }
          children[i].innerHTML = newHTML;
        }
      }
      lineElm.innerHTML = '
'+
        '
'+ lineElm.innerHTML  +
        '

';
    });
  };

  // Determines the line and column offset for a text selection.
  // @param {HTML Node} node The selected node.
  // @param {int} offset The offset of the selected node.
  // @return {simple object} The line and column of the selection.
  var getSelectionOffsets = function(node, offset){
    var parentJQ = $(node.parentNode);
    return {
      line: parseInt(parentJQ.data('line')),
      col:  parseInt(parentJQ.data('col'))
    };
    // var shElm = node.parentNode;
    // var lineElm = $(shElm).parents('.line')[0];
    // var lineNumber = parseInt(lineElm.className.split(/\s+/)[1].substr(6));
    // var colNumber = 1;
    // var children = lineElm.childNodes;
    // var i;

    // // Calculate the column.
    // for(i = 0; i < children.length && children[i] != shElm; i++){
    //   if(children[i].nodeType == 3){
    //     colNumber += children[i].nodeValue.length;
    //   } else {
    //     colNumber += children[i].innerText.length;
    //   }
    // }
    // colNumber += offset;

    // return {line: lineNumber, col: colNumber};
  };

  // Calculates the line and column offsets for the current selection.
  // @return {simple object} The starting column
  var getSelectionLocation = function(){
    var selection = window.getSelection();

    console.log(selection);

    if(!selection || selection.isCollapsed){
      return false;
    }

    var startLocInfo = getSelectionOffsets(selection.anchorNode,
      selection.anchorOffset);
    var endLocInfo = getSelectionOffsets(selection.extentNode,
      selection.extentOffset);
    return {
      start_line:   startLocInfo.line,
      start_column: startLocInfo.col,
      end_line:     endLocInfo.line,
      end_column:   endLocInfo.col
    };
  };

  // Escapes an HTML string.
  // Lifted from Mustache
  // (https://github.com/janl/mustache.js/blob/master/mustache.js)
  // @param {string} html The HTML string to escape.
  // @return The escaped HTML string.
  var escapeHtml = function(html) {
    return String(html).replace(/[&<>"'\/]/g, function (s) {
      return ENTITY_MAP[s];
    });
  };

  // Extracts the extension from a file name.
  // @param {string} filename The name of the file.
  // @return The extension (last .XXX) or undefined if no extension is found.
  var extractExtension = function(filename){
    var parts = filename.split('.');
    if(parts.length > 1){
      return parts[parts.length-1];
    }
    return undefined;
  };

  // Figures out the highlighter class for the given file.
  // @param {string} filename The name of the file.
  // @return The highlighter class corresponding to the extension; defaults to
  //         the plain text highligher if no extension is found, or no
  //         corresponding highlighter is found.
  var getHighlighterClass = function(filename){
    var extension = extractExtension(filename);
    var returnClass = 'brush: '+
      (KNOWN_FILE_EXTENSIONS[extension] || KNOWN_FILE_EXTENSIONS['txt']);
    if(extension === 'erb' || extension === 'php'){
      returnClass += "; html-script: true";
    }
    return returnClass;
  };

  // Remove the leading # on a hash.
  // @param [string] hash The hash.
  // @return The hash with leading # removed.
  var stripHash = function(hash){
    return hash.replace( /^#/, "" );
  };

  // Fetch the content of the select file.
  // @param [int] fileId The id of the file to load.
  var displayFile = function(fileId){
    $('#file-display').html('Loading file '+ fileId +'...');

    // Convert the id to an integer (just in case someones putting something
    // funny into the hash).
    fileId = parseInt(fileId)

    // Fetch the file.
    $.ajax(FILES_API+fileId, {
      success: function(data, status){
        if(data.error){
          $('.page-header').html('ERROR');
          $('#file-display').html(data.error);
        } else {
          curFileInfo = data;
          $('.page-header').html(data.file.name);
          $('#file-display');
          $('#file-display').html(
            '
\n'+
                escapeHtml(data.file.content) +
            '\n
'
          );
          SyntaxHighlighter.highlight();
          // setTimeout(10, SyntaxHighlighter.highlight);

          addColumnsToHighlightedCode($('#file-display .code')[0])
        }
      },
      error: function(req, status, error){
          $('.page-header').html('ERROR');
          $('#file-display').html(error);
      }
    });
  };

  // Toggles the sidebar and file contents area.
  var toggleFileView = function(){
    $('.sidebar').toggleClass('sidebar-collapse');
    $('.main').toggleClass('main-collapse');
  }

  // LISTENERS

  // Listen for a row to be clicked on. A td element must specifically be
  // clicked (not a child element) to trigger the page load. For example, see
  // the project listing -- the td with the trash can does not cause the row's
  // href to be loaded.
  $('.clickable-row').click(function(event) {
    if(event.target.tagName === 'TD'){
      window.document.location = $(this).data('href');
    }
  });

  // Collapses/expands directories in the file listings.
  $(document).on('click', '.directory-name', function(){
    var elm = $(this), parent = elm.parent();

    if(parent.data('expand-state') === 'expanded'){
      parent.find('.directory').hide();
      elm.find('.collapsed').show();
      elm.find('.expanded').hide();
      parent.data('expand-state', 'collapsed');
    } else {
      parent.find('.directory').show();
      elm.find('.collapsed').hide();
      elm.find('.expanded').show();
      parent.data('expand-state', 'expanded');
    }
  });

  // Detects when a file's content needs loading.
  if($('#file-display').size() == 1){
    // Check if the initial url contain a hash.
    if(location.hash){
      displayFile(stripHash(location.hash));
    }

    // Wait for any changes to the location hash.
    $(window).on('hashchange', function(){
      displayFile(stripHash(location.hash));
      if($('.main').hasClass('main-collapse')){
        toggleFileView();
      }
    });
  }

  $(document).on('click', '.sidebar-toggle', function(){
    toggleFileView();
  })

  // Listens for file content to be selected and then highlights it.
  $(document).on('mouseup', '.code .container', function(){
    console.log('Selection made!');
    var location = getSelectionLocation();
    console.log(location);
    if(location){
      $('#selection-menu .btn').removeClass('disabled');
      //hideHighlights();
      //highlightSelection(location);
    } else {
      hideHighlights();
      $('#selection-menu .btn').addClass('disabled');
    }
  });

  $(document).on('click', '#selection-menu .btn', function(e){
    var location = getSelectionLocation();
    highlightSelection(location);
    if(e.target.id === 'add-comment'){
      console.log('Adding comment');
      createComment(location);
    } else if(e.target.id === 'add-to-comment'){
      console.log('Adding to existing comment');
    } else if(e.target.id === 'add-alt-code'){
      console.log('Adding alternate code');
    }

  });

  $(document).on('click', '.comment-delete', function(e){
    $(this).parents('.comment').remove();
    e.preventDefault();
  });

  $(document).on('change', '.comment-body', editComment);
  $(document).on('keyup', '.comment-body', editComment);
  $(document).on('mouseup', '.comment-body', editComment);

  // INITIALIZATIONS.

  SyntaxHighlighter.defaults['toolbar'] = false;
  SyntaxHighlighter.defaults['quick-code'] = false;

  this.getSelectionLocation = getSelectionLocation;
  this.highlightSelection = highlightSelection;
  return this;
};

var oca;

jQuery(document).ready(function($){
  console.log('Hello!');
  oca = OCA($);
  console.log(oca);
})

<commit_msg>Comments are inserted in the order of the first location.<commit_after>var OCA = function($){
  // CONSTANTS / GLOBAL VARIABLES
  const FILES_API = '/api/files/';
  const KNOWN_FILE_EXTENSIONS = {
    as3:  'as3',
    sh:   'bash',
    cf:   'coldfusion',
    cs:   'csharp',
    cpp:  'cpp',
    h:    'cpp',
    hpp:  'cpp',
    c:    'cpp',
    cc:   'cpp',
    cxx:  'cpp',
    hxx:  'cpp',
    css:  'css',
    scss: 'css',
    dpk:  'delphi',
    diff: 'diff',
    patch:'patch',
    erl:  'erlang',
    gy:   'groovy',
    gsh:  'groovy',
    gvy:  'groovy',
    groovy:'groovy',
    js:   'javascript',
    json: 'javascript',
    java: 'java',
    jfx:  'javafx',
    pl:   'perl',
    php:  'php',
    txt:  'text',
    ps:   'powershell',
    py:   'python',
    rb:   'ruby',
    erb:  'ruby',
    scala:'scala',
    sql:  'sql',
    vb:   'vb',
    vbnet:'vb',
    xml:  'xml',
    html: 'xml',
    xhtml:'xml'
  };

  // Lifted from Mustache
  // (https://github.com/janl/mustache.js/blob/master/mustache.js)
  const ENTITY_MAP = {
    "&": "&",
    "<": "<",
    ">": ">",
    '"': '"',
    "'": ''',
    "/": '/'
  };

  var curFileInfo;

  // FUNCTIONS

  var editComment = function(e){
    var target = $(e.target);
    if(target.data('content') === target.html()){ return; }
    target.addClass('comment-in-edit');
    if(target.data('timeout')){
      clearTimeout(target.data('timeout'));
    }
    target.data('timeout', setTimeout(function(){
      target.removeClass('comment-in-edit');
      target.parent().find('.comment-saved').show().fadeOut(2000);
      target.data('content', target.html());
    }, 2000));
  };

  var compareLocations = function(loc1, loc2){
    if(loc1.start_location === loc2.start_location){
      return loc1.start_column - loc2.start_column;
    }
    return loc1.start_location - loc2.start_location;
  }

  var getFirstLocation = function(locations, fileId){
    var sorted = locations.sort(compareLocations);
    var i;
    for(i = 0; i < sorted.length; i++){
      if(sorted[i].file_id === fileId){
        return sorted[i];
      }
    }
  };

  var insertComment = function(comment, container){
    var inserted = false;
    container.children().each(function(i, e){
      var child = $(this);
      console.log('Comparing '+ child.data('start-line') +' and '+
        comment.data('start-line'));
      if(child.data('start-line') > comment.data('start-line') ||
          (child.data('start-line') === comment.data('start-line') &&
            child.data('start-column') > comment.data('start-column'))){
        console.log('Inserting comment before existing comment.');
        comment.insertBefore(child);
        inserted = true;
        return;
      }
    });
    // If we've reached this point, then the comment should go at the end.
    if(!inserted){
      console.log('Inserting comment at the end.');
      container.append(comment);
    }
  }

  var createComment = function(locations){
    var firstLocation = getFirstLocation(locations, curFileInfo.id);
    var comment = $('#comment-template').clone();
    comment.attr('id', '');
    comment.find('.comment-owner').html($('#current-email').html());
    comment.data('start-line', firstLocation.start_line).
            data('start-column', firstLocation.start_column);
    // Find the spot where this comment should be inserted.
    insertComment(comment, $('#comments'));
    // $('#comments').append(comment);

    comment.data('locations', locations);
    comment.find('.comment-body').focus();
  };

  // Hides all highlighted selections.
  var hideHighlights = function(){
    $('.code .selected').removeClass('selected');
  };

  var normalizeLocation = function(loc){
    var normLoc = {};
    if(loc.start_line < loc.end_line ||
        (loc.start_line === loc.end_line && loc.start_column < loc.end_column)){
      normLoc.start_line    = loc.start_line;
      normLoc.start_column  = loc.start_column;
      normLoc.end_line      = loc.end_line;
      normLoc.end_column    = loc.end_column;
    } else {
      normLoc.start_line   = loc.end_line;
      normLoc.start_column = loc.end_column+1;
      normLoc.end_line     = loc.start_line;
      normLoc.end_column   = loc.start_column;
    }
    return normLoc;
  };

  // Highlights the given comment location.
  // @param {simple object} loc An object with the fields:
  //    start_line:    The line of code to start on, base 1.
  //    start_column:  The starting column, base 1.
  //    end_line:      The line of code to end on, base 1.
  //    end_column:    The final column to highlight, base 1.
  var highlightSelection = function(loc){
    loc = normalizeLocation(loc);
    var i, j;
    for(i = loc.start_line; i <= loc.end_line; i++){
      var start = (i === loc.start_line) ? loc.start_column : 1;
      var end = (i === loc.end_line) ? loc.end_column :
        $('.content-line'+ i).size();
      console.log('Line '+ i +': '+ start +' to '+ end);
      for(j = start; j <= end; j++){
        $('#'+ i +'_'+ j).addClass('selected');
        // Highlight the endcap if the selection spans to lines below.
        // if(i !== loc.end_line){
        //   $('#'+ i +'_endcap').addClass('selected');
        // }
      }
    }
  };

  // Modifies the displayed file content to make highlighting easier.
  // @param {DOM Elmement} contentElm The element containing the file content.
  //                                  This should already be processed by
  //                                  SyntaxHighlighter.
  var addColumnsToHighlightedCode = function(contentElm){
    $(contentElm).find('.line').each(function(i,lineElm){
      var children = lineElm.childNodes;
      var i, colNo = 1;
      var lineNo = parseInt(lineElm.className.split(/\s+/)[1].substr(6));
      for(i = 0; i < children.length; i++){
        if(children[i].nodeType === 3){
          var j, newHTML = '';
          for(j = 0; j < children[i].nodeValue.length; j++){
            newHTML += ''+
              children[i].nodeValue[j] +'';
            colNo++;
          }
          $(children[i]).replaceWith(newHTML);
        } else {
          var j, newHTML = '';
          for(j = 0; j < children[i].innerText.length; j++){
            newHTML += ''+ children[i].innerText[j] +
              '';
            colNo++;
          }
          children[i].innerHTML = newHTML;
        }
      }
      lineElm.innerHTML = '
'+
        '
'+ lineElm.innerHTML  +
        '

';
    });
  };

  // Determines the line and column offset for a text selection.
  // @param {HTML Node} node The selected node.
  // @param {int} offset The offset of the selected node.
  // @return {simple object} The line and column of the selection.
  var getSelectionOffsets = function(node, offset){
    var parentJQ = $(node.parentNode);
    return {
      line: parseInt(parentJQ.data('line')),
      col:  parseInt(parentJQ.data('col'))
    };
    // var shElm = node.parentNode;
    // var lineElm = $(shElm).parents('.line')[0];
    // var lineNumber = parseInt(lineElm.className.split(/\s+/)[1].substr(6));
    // var colNumber = 1;
    // var children = lineElm.childNodes;
    // var i;

    // // Calculate the column.
    // for(i = 0; i < children.length && children[i] != shElm; i++){
    //   if(children[i].nodeType == 3){
    //     colNumber += children[i].nodeValue.length;
    //   } else {
    //     colNumber += children[i].innerText.length;
    //   }
    // }
    // colNumber += offset;

    // return {line: lineNumber, col: colNumber};
  };

  // Calculates the line and column offsets for the current selection.
  // @return {simple object} The starting column
  var getSelectionLocation = function(){
    var selection = window.getSelection();

    console.log(selection);

    if(!selection || selection.isCollapsed){
      return false;
    }

    var startLocInfo = getSelectionOffsets(selection.anchorNode,
      selection.anchorOffset);
    var endLocInfo = getSelectionOffsets(selection.extentNode,
      selection.extentOffset);
    return {
      start_line:   startLocInfo.line,
      start_column: startLocInfo.col,
      end_line:     endLocInfo.line,
      end_column:   endLocInfo.col
    };
  };

  // Escapes an HTML string.
  // Lifted from Mustache
  // (https://github.com/janl/mustache.js/blob/master/mustache.js)
  // @param {string} html The HTML string to escape.
  // @return The escaped HTML string.
  var escapeHtml = function(html) {
    return String(html).replace(/[&<>"'\/]/g, function (s) {
      return ENTITY_MAP[s];
    });
  };

  // Extracts the extension from a file name.
  // @param {string} filename The name of the file.
  // @return The extension (last .XXX) or undefined if no extension is found.
  var extractExtension = function(filename){
    var parts = filename.split('.');
    if(parts.length > 1){
      return parts[parts.length-1];
    }
    return undefined;
  };

  // Figures out the highlighter class for the given file.
  // @param {string} filename The name of the file.
  // @return The highlighter class corresponding to the extension; defaults to
  //         the plain text highligher if no extension is found, or no
  //         corresponding highlighter is found.
  var getHighlighterClass = function(filename){
    var extension = extractExtension(filename);
    var returnClass = 'brush: '+
      (KNOWN_FILE_EXTENSIONS[extension] || KNOWN_FILE_EXTENSIONS['txt']);
    if(extension === 'erb' || extension === 'php'){
      returnClass += "; html-script: true";
    }
    return returnClass;
  };

  // Remove the leading # on a hash.
  // @param [string] hash The hash.
  // @return The hash with leading # removed.
  var stripHash = function(hash){
    return hash.replace( /^#/, "" );
  };

  // Fetch the content of the select file.
  // @param [int] fileId The id of the file to load.
  var displayFile = function(fileId){
    $('#file-display').html('Loading file '+ fileId +'...');

    // Convert the id to an integer (just in case someones putting something
    // funny into the hash).
    fileId = parseInt(fileId)

    // Fetch the file.
    $.ajax(FILES_API+fileId, {
      success: function(data, status){
        if(data.error){
          $('.page-header').html('ERROR');
          $('#file-display').html(data.error);
        } else {
          curFileInfo = data;
          $('.page-header').html(data.file.name);
          $('#file-display');
          $('#file-display').html(
            '
\n'+
                escapeHtml(data.file.content) +
            '\n
'
          );
          SyntaxHighlighter.highlight();
          // setTimeout(10, SyntaxHighlighter.highlight);

          addColumnsToHighlightedCode($('#file-display .code')[0])
        }
      },
      error: function(req, status, error){
          $('.page-header').html('ERROR');
          $('#file-display').html(error);
      }
    });
  };

  // Toggles the sidebar and file contents area.
  var toggleFileView = function(){
    $('.sidebar').toggleClass('sidebar-collapse');
    $('.main').toggleClass('main-collapse');
  }

  // LISTENERS

  // Listen for a row to be clicked on. A td element must specifically be
  // clicked (not a child element) to trigger the page load. For example, see
  // the project listing -- the td with the trash can does not cause the row's
  // href to be loaded.
  $('.clickable-row').click(function(event) {
    if(event.target.tagName === 'TD'){
      window.document.location = $(this).data('href');
    }
  });

  // Collapses/expands directories in the file listings.
  $(document).on('click', '.directory-name', function(){
    var elm = $(this), parent = elm.parent();

    if(parent.data('expand-state') === 'expanded'){
      parent.find('.directory').hide();
      elm.find('.collapsed').show();
      elm.find('.expanded').hide();
      parent.data('expand-state', 'collapsed');
    } else {
      parent.find('.directory').show();
      elm.find('.collapsed').hide();
      elm.find('.expanded').show();
      parent.data('expand-state', 'expanded');
    }
  });

  // Detects when a file's content needs loading.
  if($('#file-display').size() == 1){
    // Check if the initial url contain a hash.
    if(location.hash){
      displayFile(stripHash(location.hash));
    }

    // Wait for any changes to the location hash.
    $(window).on('hashchange', function(){
      displayFile(stripHash(location.hash));
      if($('.main').hasClass('main-collapse')){
        toggleFileView();
      }
    });
  }

  $(document).on('click', '.sidebar-toggle', function(){
    toggleFileView();
  })

  // Listens for file content to be selected and then highlights it.
  $(document).on('mouseup', '.code .container', function(){
    console.log('Selection made!');
    var location = getSelectionLocation();
    console.log(location);
    if(location){
      $('#selection-menu .btn').removeClass('disabled');
      //hideHighlights();
      //highlightSelection(location);
    } else {
      hideHighlights();
      $('#selection-menu .btn').addClass('disabled');
    }
  });

  $(document).on('click', '#selection-menu .btn', function(e){
    var location = getSelectionLocation();
    highlightSelection(location);
    if(e.target.id === 'add-comment'){
      console.log('Adding comment');
      createComment([location]);
    } else if(e.target.id === 'add-to-comment'){
      console.log('Adding to existing comment');
    } else if(e.target.id === 'add-alt-code'){
      console.log('Adding alternate code');
    }

  });

  $(document).on('click', '.comment-delete', function(e){
    $(this).parents('.comment').remove();
    e.preventDefault();
  });

  $(document).on('change', '.comment-body', editComment);
  $(document).on('keyup', '.comment-body', editComment);
  $(document).on('mouseup', '.comment-body', editComment);

  // INITIALIZATIONS.

  SyntaxHighlighter.defaults['toolbar'] = false;
  SyntaxHighlighter.defaults['quick-code'] = false;

  this.getSelectionLocation = getSelectionLocation;
  this.highlightSelection = highlightSelection;
  return this;
};

var oca;

jQuery(document).ready(function($){
  console.log('Hello!');
  oca = OCA($);
  console.log(oca);
})

<|endoftext|>


<commit_before><commit_msg>cleanup<commit_after><|endoftext|>