<commit_before>NANO.plugin(function (NANO) {
  var search = NANO.search = {

    _each: {},
    _collection: {},
    _ready_callbacks: [],

    _init_sequence: [
      "airlines",
      "airports",
      "currency_rates",
      "closest_cities",
      "gates_info",
      "metadata",
      "minimal_prices",
      "params_attributes",
      "request_params",
      "tickets"
    ],

    _params_list: [
      "origin_iata",
      "destination_iata",
      "origin_name",
      "destination_name",
      "depart_date",
      "return_date",
      "trip_class",
      "range",
      "adults",
      "children",
      "infants",
      "marker"
    ],

    new_search: function(search_data){
      var self = this;
      var request_params = {};
      _.each(this._params_list, function(param_name){
        request_params[param_name] = search_data.data[param_name];
      });
      var ajax_params = {
        url: NANO.routes.search_path,
        type: NANO.routes.search_method,
        dataType: 'json',
        data: request_params,
        success: function(data){
          data.request_params = request_params;
          self.create(data, search_data.success);
        },
        error: search_data.error
      };
      NANO.ajax(ajax_params);
    },

    create: function (json, complete) {
      var search_results = _.extend({}, json);
      var self = this;
      var worker = null;

      _.each(this._init_sequence, function(field_name){
        var Collection = self._collection[field_name];
        var params = {
          data: search_results[field_name] || [],
          after: function(results){
            if(Collection){
              return new Collection(results);
            }
            return results;
          },
          complete: function(results){
            search_results[field_name] = results;
          }
        };
        if(self._each[field_name]){
          var Item = self._each[field_name];
          params.iterator = function(data){
            return new Item(data, search_results);
          };
        }
        if(!worker){
          worker = NANO.asyncWorker(params);
        }else{
          worker = worker.then(params);
        }
      });

      worker.then({
        data: search_results,
        complete: function(search_results){
          _.each(self._ready_callbacks, function(callback){
            callback(search_results);
          });
          complete(search_results);
        }
      });

      return worker;
    },

    each: function(field, constructor){
      this._each[field] = constructor;
    },

    collection: function (field, constructor){
      this._collection[field] = constructor;
    },

    customize: function (process_type, name, handler) {
      var constructor = this["_" + process_type][name];
      handler.call(constructor.prototype, constructor.prototype);
    },
    ready: function(callback){
      this._ready_callbacks.push(callback);
    }
  };

  var Data = function(json){
    this._data = json;
  };
  Data.prototype = {
    get: function(id){
      return id ? this._data[id] : this._data;
    }
  };

  search.collection("airlines", function(airlines){
    NANO.airlines.update(airlines);
    return NANO.airlines;
  });
  search.collection("airports", function(airports){
    NANO.airports.update(airports);
    return NANO.airports;
  });
  search.collection("closest_cities", Data);
  search.collection("minimal_prices", Data);
  search.collection("params_attributes", Data);
  search.collection("request_params", Data);

  search.collection("currency_rates", function(rates){
    NANO.currency.update_rates(rates);
    return rates;
  });

  search.collection("gates_info", function(gates_info){
    NANO.gates.update(gates_info);
    return NANO.gates;
  });

  var memoize = function(this_object){
    _.each(this_object.memoize, function(method_name){
      this_object[method_name] = _.memoize(this_object[method_name]);
    });
  };

  var Flights = function(flights_array, direction){
    this.direction = direction;
    this.flights = _.map(flights_array, function(flight_json){
      return new Flight(flight_json, direction);
    });
    memoize(this);
  };
  Flights.prototype = {
    memoize: ['airlines', 'aircompany', 'duration', 'stops_count', 'stops_durations', 'stops_iatas'],
    get_flights_values: function(field_name, is_function, params){
      return _.map(this.flights, function(flight){
        return is_function ? flight[field_name].apply(flight, params) : flight.data[field_name];
      });
    },
    list: function(){
      return this.flights;
    },
    origin: function(){
      return this.flights[0].origin();
    },
    destination: function(){
      return _.last(this.flights).destination();
    },
    duration: function(){
      return _.reduce(this.flights, function(duration, flight){
        return duration + flight.data.duration + flight.data.delay;
      }, 0);
    },
    numbers: function(){
      return this.get_flights_values("number");
    },
    airlines: function(){
      return _.uniq(this.get_flights_values("airline"));
    },

    airline: function(){
      return _(this.list()).chain().
        reduce(function(hash, flight){
          var airc = flight.airline();
          hash[airc] = hash.hasOwnProperty(airc) ? hash[airc] + flight.duration() : flight.duration();
          return hash;
        }, {}).
        map(function(flight_duration, aircompany_iata){
          return {
            duration: flight_duration,
            iata: aircompany_iata
          };
        }).
        max(function(info){
          return info.duration;
        }).
        value();
    },
    depart_timestamp: function(){
      return this.flights[0].depart_timestamp();
    },
    arrival_timestamp: function(){
      return _.last(this.flights).arrival_timestamp();
    },
    stops_iatas: function(){
      return _.map(this.stops(), function(stop_data){
        return stop_data.iata;
      });
    },
    stops: function(){
      return _.compact(this.get_flights_values("stop_data", true));
    },
    stops_count: function(){
      return Math.max(this.flights.length - 1, 0);
    },
    stops_durations: function(){
      return _.compact(_.uniq(this.get_flights_values("delay")));
    }
  };

  var Flight = function(flight_json, direction){
    this.direction = direction;
    this.data = flight_json;
  };
  Flight.prototype = {
    duration: function(){
      return this.data.duration;
    },
    number: function(){
      return this.data.number;
    },
    airline: function(){
      return this.data.airline;
    },
    origin: function(){
      return this.data.origin;
    },
    destination: function(){
      return this.data.destination;
    },
    depart_timestamp: function(){
      return this.data.departure * 1000;
    },
    arrival_timestamp: function(){
      return this.data.arrival * 1000;
    },
    stop_data: function(){
      if(this.data.delay && 0 < this.data.delay){
        return {
          iata: this.data.origin,
          delay: this.data.delay
        };
      }else{
        return null;
      }
    }
  };
  //добавляем сами билеты
  var Ticket = function(ticket_json, search){
    this.search = search;
    this.id = _.uniqueId("ticket_");
    this._data = ticket_json;
    var self = this;
    _.each(["direct", "return"], function(direction){
      var name = direction + "_flights";
      self[name] = ticket_json[name] ? new Flights(ticket_json[name], direction) : false;
    });
    _.each(this.init_stack, function(method_name){
      self[method_name]();
    });
    memoize(this);
  };
  Ticket.prototype = {
    fn: Ticket.prototype,
    init_stack: [],
    memoize: [],
    _default_url: function(url_id){
      return '/nano/clicks/new?search_id=' + this.search.search_id + '&url_id=' + url_id;
    },
    main_airline: function(){
      var direct_airline = this.direct_flights.airline();
      var return_airline = this.return_flights ? this.return_flights.airline() : {};
      return _.max([direct_airline, return_airline], function(airline){
        return airline.duration;
      });
    },
    send_gate_data: function(gate_id){
      if(!urls[id]){
        return;
      }
      var gate = _.detect(this.gates(), function(gate){
        return gate.id === gate_id;
      });
      NANO.ajax({
        url: this._default_url(gate.url_id) + '.js',
        type: "get",
        data: {
          click: {
            gate_id: gate.id,
            proposal_price: NANO.currency.to_default(gate.price)
          }
        }
      });
    },
    gates: function(){
      var ticket_gates = [];
      var self = this;
      var prices = this._data.native_prices;
      var gates = this.search.gates_info;
      var search = this.search;
      var urls = this.direct_order_urls;

      _.each(this._data.order_urls, function(url_id, id){
        var gate = gates.get(id);
        var gate_data = {
          ticket_id: self.id,
          id: id,
          name: gate.label,
          url_id: url_id,
          is_airline: gate.is_airline,
          payment_methods: gate.payment_methods,
          price: {
            currency: gate.currency_code,
            value: prices[id]
          },
          default_currency_price: NANO.currency.to_default(prices[id], gate.currency_code)
        };
        if(urls && urls[id]){
          gate_data.url = urls[id] + "|" + search.params.search.marker;
          gate_data.type = "direct";
        }else{
          gate_data.url = self._default_url(url_id);
          gate_data.type = "redirect";
        }
        ticket_gates.push(gate_data);
      });

      ticket_gates = ticket_gates.sort(function(gate_a, gate_b){
        return gate_a.default_currency_price.value < gate_b.default_currency_price.value ? (-1) : (1);
      });

      var param_cache = {};

      this.gates = function(param){
        if(param){
          if(!param_cache[param]){
            param_cache[param] =  _.map(ticket_gates, function(gate){
              return gate[param];
            });
          }
          return param_cache[param];
        }
        return ticket_gates.concat([]);
      };
      return this.gates();
    },
    minimal_price: function(){
      return _.min(this.gates("default_currency_price"), function(price){
        return price.value;
      }).value;
    }
  };
  search.each("tickets", Ticket);

  var Tickets = function(tickets, params){
    this.list = tickets;
    if(params){
      _.extend(this, params);
    }
    var self = this;
    _.each(this.list, function(ticket){
      ticket.tickets = self;
    });
    _.each(this.init_stack, function(method){
      method.call(self);
    });
    this._tickets_index = 0;
  };
  Tickets.prototype = {
    next_tickets_count: 10,
    _constructor: Tickets,
    fn: Tickets.prototype,
    init_stack: [],
    _self: function(tickets, params){
      return new this._constructor(tickets, params);
    },
    get: function(id){
      if(_.isString(id)){
        return _.detect(this.list, function(ticket){
          return ticket.id === id;
        });
      }
      return this.list;
    },
    next: function(count){
      var start = this._tickets_index;
      var end = start + (count || this.next_tickets_count);
      this._tickets_index = end;
      return this.list.slice(start, end);
    }
  };
  search.collection("tickets", Tickets);
});<commit_msg>Use url helpers in js<commit_after><|endoftext|>


<commit_before>const Rule = require('./base');
const {getNodeName, classifyNode} = require('../helpers');

module.exports = class MarkUsedHelpers extends Rule {
    _markUsedHelpers(node) {
        const nodeName = getNodeName(node);
        const helperType = classifyNode(node);

        // helper nodes will break the rendering if there is no matching helper
        // ambiguous nodes simply won't appear if there is no matching helper and no matching context
        if (helperType === 'helper' || helperType === 'ambiguous') {
            this.scanner.context.partials.push({
                node: nodeName,
                type: node.type,
                helperType,
                loc: node.loc,
                parameters: node.params ? node.params.map(p => p.original) : null
            });
        }
    }

    visitor() {
        return {
            BlockStatement: this._markUsedHelpers.bind(this),
            MustacheStatement: this._markUsedHelpers.bind(this),
            SubExpression: this._markUsedHelpers.bind(this)
        };
    }
};
<commit_msg>Fixed incorrect variable name from previous commit<commit_after>const Rule = require('./base');
const {getNodeName, classifyNode} = require('../helpers');

module.exports = class MarkUsedHelpers extends Rule {
    _markUsedHelpers(node) {
        const nodeName = getNodeName(node);
        const helperType = classifyNode(node);

        // helper nodes will break the rendering if there is no matching helper
        // ambiguous nodes simply won't appear if there is no matching helper and no matching context
        if (helperType === 'helper' || helperType === 'ambiguous') {
            this.scanner.context.helpers.push({
                node: nodeName,
                type: node.type,
                helperType,
                loc: node.loc,
                parameters: node.params ? node.params.map(p => p.original) : null
            });
        }
    }

    visitor() {
        return {
            BlockStatement: this._markUsedHelpers.bind(this),
            MustacheStatement: this._markUsedHelpers.bind(this),
            SubExpression: this._markUsedHelpers.bind(this)
        };
    }
};
<|endoftext|>


<commit_before>/**
 * @author Ryan Johnson <http://syntacticx.com/>
 * @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
 * @package LivePipe UI
 * @license MIT
 * @url http://livepipe.net/control/scrollbar
 * @require prototype.js, slider.js, livepipe.js
 */

if(typeof(Prototype) == "undefined")
    throw "Control.ScrollBar requires Prototype to be loaded.";
if(typeof(Control.Slider) == "undefined")
    throw "Control.ScrollBar requires Control.Slider to be loaded.";
if(typeof(Object.Event) == "undefined")
    throw "Control.ScrollBar requires Object.Event to be loaded.";

Control.ScrollBar = Class.create({
    initialize: function(container,track,options){
        Control.ScrollBar.instances.push(this);
        this.enabled = false;
        this.notificationTimeout = false;
        this.container = $(container);
        this.boundMouseWheelEvent = this.onMouseWheel.bindAsEventListener(this);
        this.boundResizeObserver = this.onWindowResize.bind(this);
        this.track = $(track);
        this.handle = this.track.firstDescendant();
        this.options = Object.extend({
            active_class_name: 'scrolling',
            apply_active_class_name_to: this.container,
            notification_timeout_length: 125,
            handle_minimum_length: 25,
            scroll_to_smoothing: 0.01,
            scroll_to_steps: 15,
            scroll_to_precision: 100,
            proportional: true,
            custom_event: null,
            custom_event_handler: null,
            scroll_axis: 'vertical',
            slider_options: {}
        },options || {});
        this.slider = new Control.Slider(this.handle,this.track,Object.extend({
            axis: this.options.scroll_axis,
            onSlide: this.onChange.bind(this),
            onChange: this.onChange.bind(this)
        },this.options.slider_options));
        this.recalculateLayout();
        Event.observe(window,'resize',this.boundResizeObserver);
        if (this.options.custom_event) {
            if (Object.isFunction(this.options.custom_event_handler)) {
                this.container.observe(this.options.custom_event, this.options.custom_event_handler);
            } else {
                this.container.observe(this.options.custom_event, this.boundResizeObserver);
            }
        }
        this.handle.observe('mousedown',function(){
            if(this.auto_sliding_executer)
                this.auto_sliding_executer.stop();
        }.bind(this));
    },
    destroy: function(){
        Event.stopObserving(window,'resize',this.boundResizeObserver);
        if(this.options.active_class_name)
            $(this.options.apply_active_class_name_to).removeClassName(this.options.active_class_name);
        if (this.options.custom_event) {
            this.container.stopObserving(this.options.custom_event);
        }
    },
    scrollLength: function(){
        return (this.options.scroll_axis == 'vertical') ? this.container.scrollHeight : this.container.scrollWidth;
    },
    offsetLength: function(){
        return (this.options.scroll_axis == 'vertical') ? this.container.offsetHeight : this.container.offsetWidth;
    },
    enable: function(){
        this.enabled = true;
        this.container.observe('mouse:wheel',this.boundMouseWheelEvent);
        this.slider.setEnabled();
        this.track.show();
        if(this.options.active_class_name)
            $(this.options.apply_active_class_name_to).addClassName(this.options.active_class_name);
        this.notify('enabled');
    },
    disable: function(){
        this.enabled = false;
        this.container.stopObserving('mouse:wheel',this.boundMouseWheelEvent);
        this.slider.setDisabled();
        this.track.hide();
        if(this.options.active_class_name)
            $(this.options.apply_active_class_name_to).removeClassName(this.options.active_class_name);
        this.notify('disabled');
        this.reset();
    },
    reset: function(){
        this.slider.setValue(0);
    },
    recalculateLayout: function(){
        if(this.scrollLength() <= this.offsetLength())
            this.disable();
        else{
            this.enable();
            this.slider.trackLength = this.slider.maximumOffset() - this.slider.minimumOffset();
            if(this.options.proportional){
                this.slider.handleLength = Math.max(this.offsetLength() * (this.offsetLength() / this.scrollLength()),this.options.handle_minimum_length);
                if (this.options.scroll_axis == 'vertical')
                    this.handle.style.height = this.slider.handleLength + 'px';
                else
                    this.handle.style.width = this.slider.handleLength + 'px';
            }
            this.scrollBy(0);
        }
    },
    onWindowResize: function(){
        this.recalculateLayout();
        this.scrollBy(0);
    },
    onMouseWheel: function(event){
        if(this.auto_sliding_executer)
            this.auto_sliding_executer.stop();
        // Move the content by 30 pixels each wheel event
        this.slider.setValueBy(-(30 * event.memo.delta / (this.scrollLength()-this.slider.trackLength)));
        event.stop();
        return false;
    },
    onChange: function(value){
        var scroll_pos = Math.round(value / this.slider.maximum * (this.scrollLength() - this.offsetLength()));
        if (this.options.scroll_axis == 'vertical')
            this.container.scrollTop = scroll_pos;
        else
            this.container.scrollLeft = scroll_pos;
        if(this.notification_timeout)
            window.clearTimeout(this.notificationTimeout);
        this.notificationTimeout = window.setTimeout(function(){
            this.notify('change',value);
        }.bind(this),this.options.notification_timeout_length);
    },
    getCurrentMaximumDelta: function(){
        return this.slider.maximum * (this.scrollLength() - this.offsetLength());
    },
    getContainerOffset: function(element) {
        var offset = element.positionedOffset();
        while (element.getOffsetParent() != this.container)
        {
            element         = element.getOffsetParent();
            offset[0]      += element.positionedOffset()[0];
            offset[1]      += element.positionedOffset()[1];
            offset.top     += element.positionedOffset().top;
            offset.left    += element.positionedOffset().left;
        }
        return offset;
    },
    getDeltaToElement: function(element){

        if (this.options.scroll_axis == 'vertical')
            return this.slider.maximum * ((this.getContainerOffset(element).top + (element.getHeight() / 2)) - (this.container.getHeight() / 2));
        else
            return this.slider.maximum * ((this.getContainerOffset(element).left + (element.getWidth() / 2)) - (this.container.getWidth() / 2));
    },
    scrollTo: function(y,animate){
        var precision = this.options.scroll_to_precision,
            current_maximum_delta = this.getCurrentMaximumDelta();
        if (precision == 'auto')
            precision = Math.pow(10, Math.ceil(Math.log(current_maximum_delta)/Math.log(10)));
        if(y == 'top')
            y = 0;
        else if(y == 'bottom')
            y = current_maximum_delta;
        else if(typeof(y) != "number")
            y = this.getDeltaToElement($(y));
        if(this.enabled){
            y = Math.max(0,Math.min(y,current_maximum_delta));
            if(this.auto_sliding_executer)
                this.auto_sliding_executer.stop();
            var target_value = y / current_maximum_delta;
            var original_slider_value = this.slider.value;
            var delta = (target_value - original_slider_value) * current_maximum_delta;
            if(animate){
                this.auto_sliding_executer = new PeriodicalExecuter(function(){
                    if(Math.round(this.slider.value * precision) / precision < Math.round(target_value * precision) / precision || Math.round(this.slider.value * precision) / precision > Math.round(target_value * precision) / precision){
                        this.scrollBy(delta / this.options.scroll_to_steps);
                    }else{
                        this.auto_sliding_executer.stop();
                        this.auto_sliding_executer = null;
                        if(typeof(animate) == "function")
                            animate();
                    }
                }.bind(this),this.options.scroll_to_smoothing);
            }else
                this.scrollBy(delta);
        }else if(typeof(animate) == "function")
            animate();
    },
    scrollBy: function(y){
        if(!this.enabled)
            return false;
        this.slider.setValueBy(y / (this.getCurrentMaximumDelta() == 0 ? 1 : this.getCurrentMaximumDelta()) );
    }
});
Object.extend(Control.ScrollBar,
{
    instances: [],

    findByElementId: function(id)
    {
        return Control.ScrollBar.instances.find(function(instance)
        {
            return (instance.container.id && instance.container.id == id);
        });
    }
});
Object.Event.extend(Control.ScrollBar);
<commit_msg>Fixed scroll distance not hardcoded anymore, can now be set when initializing the ScrollBar. Disabled by default for keeping the old behaviour due to compatibility reasons (if this is desired, otherwise please change (I personally would ;) )).<commit_after>/**
 * @author Ryan Johnson <http://syntacticx.com/>
 * @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
 * @package LivePipe UI
 * @license MIT
 * @url http://livepipe.net/control/scrollbar
 * @require prototype.js, slider.js, livepipe.js
 */

if(typeof(Prototype) == "undefined")
    throw "Control.ScrollBar requires Prototype to be loaded.";
if(typeof(Control.Slider) == "undefined")
    throw "Control.ScrollBar requires Control.Slider to be loaded.";
if(typeof(Object.Event) == "undefined")
    throw "Control.ScrollBar requires Object.Event to be loaded.";

Control.ScrollBar = Class.create({
    initialize: function(container,track,options){
        Control.ScrollBar.instances.push(this);
        this.enabled = false;
        this.notificationTimeout = false;
        this.container = $(container);
        this.boundMouseWheelEvent = this.onMouseWheel.bindAsEventListener(this);
        this.boundResizeObserver = this.onWindowResize.bind(this);
        this.track = $(track);
        this.handle = this.track.firstDescendant();
        this.options = Object.extend({
            active_class_name: 'scrolling',
            apply_active_class_name_to: this.container,
            notification_timeout_length: 125,
            handle_minimum_length: 25,
            scroll_to_smoothing: 0.01,
            scroll_to_steps: 15,
            scroll_to_precision: 100,
            proportional: true,
            custom_event: null,
            custom_event_handler: null,
            scroll_axis: 'vertical',
            /**
             * If this value is larger than 0 (e.g. 50), one mouse wheel event will scroll the page
             * by the given number of pixels (default behaviour in any program). If it is lower than 0,
             * the scrollbar's default behaviour will be used (scrolls down 1/20 of the page, no matter
             * how long it is). Initialization with e.g.:
             * new Control.ScrollBar(
                    document.getElementById('scrollbar_content'),
                    document.getElementById('scrollbar_track'),
                    { fixed_scroll_distance: 50 });
             */
            fixed_scroll_distance: -1,
            slider_options: {}
        },options || {});
        this.slider = new Control.Slider(this.handle,this.track,Object.extend({
            axis: this.options.scroll_axis,
            onSlide: this.onChange.bind(this),
            onChange: this.onChange.bind(this)
        },this.options.slider_options));
        this.recalculateLayout();
        Event.observe(window,'resize',this.boundResizeObserver);
        if (this.options.custom_event) {
            if (Object.isFunction(this.options.custom_event_handler)) {
                this.container.observe(this.options.custom_event, this.options.custom_event_handler);
            } else {
                this.container.observe(this.options.custom_event, this.boundResizeObserver);
            }
        }
        this.handle.observe('mousedown',function(){
            if(this.auto_sliding_executer)
                this.auto_sliding_executer.stop();
        }.bind(this));
    },
    destroy: function(){
        Event.stopObserving(window,'resize',this.boundResizeObserver);
        if(this.options.active_class_name)
            $(this.options.apply_active_class_name_to).removeClassName(this.options.active_class_name);
        if (this.options.custom_event) {
            this.container.stopObserving(this.options.custom_event);
        }
    },
    scrollLength: function(){
        return (this.options.scroll_axis == 'vertical') ? this.container.scrollHeight : this.container.scrollWidth;
    },
    offsetLength: function(){
        return (this.options.scroll_axis == 'vertical') ? this.container.offsetHeight : this.container.offsetWidth;
    },
    enable: function(){
        this.enabled = true;
        this.container.observe('mouse:wheel',this.boundMouseWheelEvent);
        this.slider.setEnabled();
        this.track.show();
        if(this.options.active_class_name)
            $(this.options.apply_active_class_name_to).addClassName(this.options.active_class_name);
        this.notify('enabled');
    },
    disable: function(){
        this.enabled = false;
        this.container.stopObserving('mouse:wheel',this.boundMouseWheelEvent);
        this.slider.setDisabled();
        this.track.hide();
        if(this.options.active_class_name)
            $(this.options.apply_active_class_name_to).removeClassName(this.options.active_class_name);
        this.notify('disabled');
        this.reset();
    },
    reset: function(){
        this.slider.setValue(0);
    },
    recalculateLayout: function(){
        if(this.scrollLength() <= this.offsetLength())
            this.disable();
        else{
            this.enable();
            this.slider.trackLength = this.slider.maximumOffset() - this.slider.minimumOffset();
            if(this.options.proportional){
                this.slider.handleLength = Math.max(this.offsetLength() * (this.offsetLength() / this.scrollLength()),this.options.handle_minimum_length);
                if (this.options.scroll_axis == 'vertical')
                    this.handle.style.height = this.slider.handleLength + 'px';
                else
                    this.handle.style.width = this.slider.handleLength + 'px';
            }
            this.scrollBy(0);
        }
    },
    onWindowResize: function(){
        this.recalculateLayout();
        this.scrollBy(0);
    },
    onMouseWheel: function(event){
        if(this.auto_sliding_executer) {
            this.auto_sliding_executer.stop();
        }
        if (this.options.fixed_scroll_distance > 0) {
            // Move the content by the given number of pixels each wheel event
            this.slider.setValueBy(-(this.options.fixed_scroll_distance * event.memo.delta / (this.scrollLength()-this.slider.trackLength)));
        } else {
            // Move the content by 1/20 of the page
            this.slider.setValueBy(-(event.memo.delta / 20));
        }
        event.stop();
        return false;
    },
    onChange: function(value){
        var scroll_pos = Math.round(value / this.slider.maximum * (this.scrollLength() - this.offsetLength()));
        if (this.options.scroll_axis == 'vertical')
            this.container.scrollTop = scroll_pos;
        else
            this.container.scrollLeft = scroll_pos;
        if(this.notification_timeout)
            window.clearTimeout(this.notificationTimeout);
        this.notificationTimeout = window.setTimeout(function(){
            this.notify('change',value);
        }.bind(this),this.options.notification_timeout_length);
    },
    getCurrentMaximumDelta: function(){
        return this.slider.maximum * (this.scrollLength() - this.offsetLength());
    },
    getContainerOffset: function(element) {
        var offset = element.positionedOffset();
        while (element.getOffsetParent() != this.container)
        {
            element         = element.getOffsetParent();
            offset[0]      += element.positionedOffset()[0];
            offset[1]      += element.positionedOffset()[1];
            offset.top     += element.positionedOffset().top;
            offset.left    += element.positionedOffset().left;
        }
        return offset;
    },
    getDeltaToElement: function(element){

        if (this.options.scroll_axis == 'vertical')
            return this.slider.maximum * ((this.getContainerOffset(element).top + (element.getHeight() / 2)) - (this.container.getHeight() / 2));
        else
            return this.slider.maximum * ((this.getContainerOffset(element).left + (element.getWidth() / 2)) - (this.container.getWidth() / 2));
    },
    scrollTo: function(y,animate){
        var precision = this.options.scroll_to_precision,
            current_maximum_delta = this.getCurrentMaximumDelta();
        if (precision == 'auto')
            precision = Math.pow(10, Math.ceil(Math.log(current_maximum_delta)/Math.log(10)));
        if(y == 'top')
            y = 0;
        else if(y == 'bottom')
            y = current_maximum_delta;
        else if(typeof(y) != "number")
            y = this.getDeltaToElement($(y));
        if(this.enabled){
            y = Math.max(0,Math.min(y,current_maximum_delta));
            if(this.auto_sliding_executer)
                this.auto_sliding_executer.stop();
            var target_value = y / current_maximum_delta;
            var original_slider_value = this.slider.value;
            var delta = (target_value - original_slider_value) * current_maximum_delta;
            if(animate){
                this.auto_sliding_executer = new PeriodicalExecuter(function(){
                    if(Math.round(this.slider.value * precision) / precision < Math.round(target_value * precision) / precision || Math.round(this.slider.value * precision) / precision > Math.round(target_value * precision) / precision){
                        this.scrollBy(delta / this.options.scroll_to_steps);
                    }else{
                        this.auto_sliding_executer.stop();
                        this.auto_sliding_executer = null;
                        if(typeof(animate) == "function")
                            animate();
                    }
                }.bind(this),this.options.scroll_to_smoothing);
            }else
                this.scrollBy(delta);
        }else if(typeof(animate) == "function")
            animate();
    },
    scrollBy: function(y){
        if(!this.enabled)
            return false;
        this.slider.setValueBy(y / (this.getCurrentMaximumDelta() == 0 ? 1 : this.getCurrentMaximumDelta()) );
    }
});
Object.extend(Control.ScrollBar,
{
    instances: [],

    findByElementId: function(id)
    {
        return Control.ScrollBar.instances.find(function(instance)
        {
            return (instance.container.id && instance.container.id == id);
        });
    }
});
Object.Event.extend(Control.ScrollBar);
<|endoftext|>


<commit_before>const structure = require('./structure');
const remark = require('remark');
const strip = require('strip-markdown');
const path = require('path');
const fs = require('fs-extra');

const searchmap = module.exports;

searchmap.generate = function(conf, struct) {
	const map = new SearchMap(conf, struct);
	fs.mkdirsSync(conf.distSrcPath);
	const file = path.join(conf.distSrcPath, 'searchmap.json');
	console.log(`Writing search map: ${file}...`);
	fs.writeFileSync(file, JSON.stringify(map.articles));
};

const SearchMap = function(conf, structure) {
	this.stripper = remark().use(strip);
	this.articles = [];
	structure.sections.map((section) => {
		traverse(section, this);
	});
};

SearchMap.prototype.addArticle = function(article) {
	let stripped = String(this.stripper.processSync(article.content));

	// Clear the contents of any `if` and `comment` tags
	// Remove other tags but leave their contents
	const tagsRegExp = /{% if.*?endif %}|{% comment.*?endcomment %}|{%[^{}]*%}/gms;
	stripped = stripped.replace(tagsRegExp, '');

	this.articles.push({
		id: article.id,
		title: article.title,
		slug: article.slug,
		url: article.url,
		section: section(article),
		category: category(article),
		content: String(stripped),
		updated: new Date().toISOString(),
		roles: article.roles,
		scope: article.scope,
		author: article.author
	});
};

function traverse(node, map) {
	node.children.forEach((child) => {
		switch (child.type) {
		case structure.TYPE.CATEGORY:
			if (child.children.length > 0) {
				traverse(child, map);
			}
			break;
		case structure.TYPE.ARTICLE:
			map.addArticle(child);
			break;
		}
	});
}

function category(node) {
	return node.parent && node.parent.type === structure.TYPE.CATEGORY ? node.parent.title : '';
}

function section(node) {
	if (node.parent) {
		return node.parent.type === structure.TYPE.SECTION ? node.parent.title : section(node.parent);
	}
	return '';
}
<commit_msg>Stripped out 'for' tags as well<commit_after>const structure = require('./structure');
const remark = require('remark');
const strip = require('strip-markdown');
const path = require('path');
const fs = require('fs-extra');

const searchmap = module.exports;

searchmap.generate = function(conf, struct) {
	const map = new SearchMap(conf, struct);
	fs.mkdirsSync(conf.distSrcPath);
	const file = path.join(conf.distSrcPath, 'searchmap.json');
	console.log(`Writing search map: ${file}...`);
	fs.writeFileSync(file, JSON.stringify(map.articles));
};

const SearchMap = function(conf, structure) {
	this.stripper = remark().use(strip);
	this.articles = [];
	structure.sections.map((section) => {
		traverse(section, this);
	});
};

SearchMap.prototype.addArticle = function(article) {
	let stripped = String(this.stripper.processSync(article.content));

	// Clear the contents of any `if`, `for` and `comment` tags
	// Remove other tags but leave their contents
	const tagsRegExp = /{% if.*?endif %}|{% for.*?endfor %}|{% comment.*?endcomment %}|{%[^{}]*%}/gms;
	stripped = stripped.replace(tagsRegExp, '');

	this.articles.push({
		id: article.id,
		title: article.title,
		slug: article.slug,
		url: article.url,
		section: section(article),
		category: category(article),
		content: String(stripped),
		updated: new Date().toISOString(),
		roles: article.roles,
		scope: article.scope,
		author: article.author
	});
};

function traverse(node, map) {
	node.children.forEach((child) => {
		switch (child.type) {
		case structure.TYPE.CATEGORY:
			if (child.children.length > 0) {
				traverse(child, map);
			}
			break;
		case structure.TYPE.ARTICLE:
			map.addArticle(child);
			break;
		}
	});
}

function category(node) {
	return node.parent && node.parent.type === structure.TYPE.CATEGORY ? node.parent.title : '';
}

function section(node) {
	if (node.parent) {
		return node.parent.type === structure.TYPE.SECTION ? node.parent.title : section(node.parent);
	}
	return '';
}
<|endoftext|>


<commit_before>/*!
 * selectbox
 * Date: 2015/6/12
 * https://github.com/nuintun/beauty-form
 *
 * This is licensed under the MIT License (MIT).
 * For details, see: https://github.com/nuintun/beauty-form/blob/master/LICENSE
 */

'use strict';

require('./css/selectbox.css');

var $ = require('jquery');
var util = require('./util');

var timer;
var reference = 0;
var actived = null;
var win = $(window);
var doc = $(document);

/**
 * compile
 * @param context
 * @param template
 * @returns {string}
 */
function compile(context, template){
  var args = [].slice.call(arguments, 2);
  var html = template.apply(context, args);

  if ($.type(html) === 'string') {
    return html;
  } else {
    throw new TypeError('Render function must return a string.');
  }
}

function SelectBox(element, options){
  options = $.extend({
    title: function (element, text){
      return ''
        + '
'
        + text + '
';
    },
    dropdown: function (element, options){
      return '
' + options + '
';
    },
    optgroup: function (element, label){
      return '
' + label + '
';
    },
    option: function (element, option){
      return '
' + option.text + '
';
    },
    optionIndexAttr: 'data-option',
    optionSelectedClass: 'ui-beauty-select-option-selected',
    optionDisabledClass: 'ui-beauty-select-option-disabled'
  }, options);

  $.each(['title', 'dropdown', 'optgroup', 'option'], function (index, prop){
    if ($.type(options[prop]) !== 'function') {
      throw new TypeError('Options.' + prop + ' must be a function.');
    }
  });

  var context = this;

  context.type = 'select';
  context.opened = false;
  context.destroyed = false;
  context.element = $(element);
  context.options = options;

  context.__init();
}

/**
 * get
 * @param element
 * @returns {*}
 */
SelectBox.get = function (element){
  element = $(element);

  return element.data('beauty-select');
};

SelectBox.prototype = {
  __init: function (){
    var context = this;
    var type = context.type;
    var options = context.options;
    var namespace = '.beauty-' + type;

    actived = context;

    if (!reference) {
      var selector = 'select';

      doc.on('change' + namespace, selector, function (){
        var selectbox = SelectBox.get(this);

        if (selectbox) {
          selectbox.__renderTitlebox();
          selectbox.opened && selectbox.__refreshSelected();
        }
      });

      doc.on('focusin' + namespace + ' focusout' + namespace, selector, function (){
        var selectbox = SelectBox.get(this);

        selectbox && selectbox.__refreshSelectbox();
      });

      doc.on('mousedown' + namespace, function (e){
        var target = e.target;
        var selectbox = actived.selectbox[0];

        if (target !== selectbox && !$.contains(selectbox, target) && actived.opened) {
          actived.close();
          actived.__refreshSelectbox();
        }
      });

      doc.on('keydown' + namespace, function (e){
        if (e.which === 9 || e.which === 27) {
          actived.opened && actived.close();
          actived.__refreshSelectbox();
        }
      });

      win.on('resize' + namespace, function (){
        clearTimeout(timer);

        timer = setTimeout(function (){
          actived.opened && actived.__position();
        }, 0);
      });
    }

    context.__beauty();

    context.element.on('keypress' + namespace, function (e){
      if (e.which === 13) {
        e.preventDefault();

        if (context.opened) {
          context.close();
        } else {
          context.open();
        }
      }
    });

    context.selectbox.on('mousedown' + namespace, function (e){
      var select = context.element;

      if (select[0].disabled) return;

      e.preventDefault();

      context.focus();

      if (context.opened) {
        var target = e.target;
        var dropdown = context.dropdown[0];

        if (dropdown !== target && !$.contains(dropdown, target)) {
          context.close();
        }
      } else {
        context.open();
      }
    });

    context.selectbox.on('click' + namespace, '[' + options.optionIndexAttr + ']', function (e){
      e.preventDefault();

      var option = $(this);

      if (option.hasClass(options.optionDisabledClass)) return;

      context.select(option.attr(options.optionIndexAttr));
      context.close();
    });

    return context;
  },
  __sizeSelectbox: function (){
    var element = this.element;
    var selectbox = this.selectbox;
    var width = element.outerWidth() - selectbox.outerWidth() + selectbox.width();
    var height = element.outerHeight() - selectbox.outerHeight() + selectbox.height();

    selectbox.width(width);
    selectbox.height(height);

    return this;
  },
  __sizeDropdown: function (){
    var context = this;

    if (!context.opened) {
      context.dropdown.css('visibility', 'hidden');
      context.dropdown.appendTo(context.selectbox);
    }

    var element = context.element;
    var selectbox = context.selectbox;
    var dropdown = context.dropdown;
    var originWidth = element.outerWidth();

    element.width('auto');

    var adaptiveWidth = element.outerWidth();

    element.outerWidth(originWidth);
    dropdown.width('auto');

    var size = {
      selectbox: {
        outerWidth: selectbox.outerWidth()
      },
      dropdown: {
        width: dropdown.width(),
        outerWidth: dropdown.outerWidth()
      }
    };

    dropdown.width(Math.max(
      size.selectbox.outerWidth - size.dropdown.outerWidth + size.dropdown.width,
      adaptiveWidth - size.dropdown.outerWidth + size.dropdown.width
    ));

    if (!context.opened) {
      context.dropdown.css('visibility', 'visible');
      context.dropdown.detach();
    }

    return context;
  },
  __position: function (){
    var context = this;

    if (context.opened) {
      var selectbox = context.selectbox;
      var dropdown = context.dropdown;
      var scrollTop = win.scrollTop();
      var offset = selectbox.offset();

      var size = {
        window: { height: win.height() },
        selectbox: { outerHeight: selectbox.outerHeight() },
        dropdown: { outerHeight: dropdown.outerHeight() }
      };

      var top = offset.top - scrollTop;
      var bottom = size.window.height - top - size.selectbox.outerHeight;
      var position = top > bottom ? 'top' : 'bottom';

      dropdown
        .removeClass('ui-beauty-select-dropdown-' + (position === 'top' ? 'bottom' : 'top'))
        .addClass('ui-beauty-select-dropdown-' + position);

      dropdown.css({
        top: position === 'bottom' ? size.selectbox.outerHeight : -size.dropdown.outerHeight
      });
    }

    return context;
  },
  __renderTitlebox: function (){
    var context = this;
    var element = context.element[0];
    var selected = $(element.options[element.selectedIndex]);

    context.titlebox.html(compile(
      context,
      context.options.title,
      selected,
      selected.html()
    ));

    return context;
  },
  __renderDropdown: function (){
    var index = 0;
    var dropdown = '';
    var context = this;
    var options = context.options;
    var selectedIndex = context.element[0].selectedIndex;

    function option(element, group){
      var selected = index === selectedIndex;
      var option = {
        group: group,
        index: index++,
        text: element.html(),
        indexAttr: options.optionIndexAttr,
        className: element[0].disabled
          ? options.optionDisabledClass
          : ( selected ? options.optionSelectedClass : '')
      };

      dropdown += compile(
        context,
        options.option,
        element,
        option
      );
    }

    function optgroup(element){
      dropdown += compile(
        context,
        options.optgroup,
        element,
        element.attr('label')
      );
    }

    var items = context.element.children();

    items.each(function (){
      var element = $(this);

      switch (this.nodeName.toLowerCase()) {
        case 'option':
          option(element);
          break;
        case 'optgroup':
          optgroup(element);
          element.children().each(function (){
            option($(this), element);
          });
          break;
      }
    });

    context.dropdown.html(compile(
      context,
      options.dropdown,
      items,
      dropdown
    ));

    return context;
  },
  __refreshSelectbox: function (){
    var context = this;
    var element = context.element[0];
    var selectbox = context.selectbox;
    var focused = util.activeElement();

    focused = context.opened
      || focused === element
      || focused === selectbox[0]
      || $.contains(selectbox[0], focused);

    selectbox
      .toggleClass('ui-beauty-select-disabled', element.disabled)
      .toggleClass('ui-beauty-select-focus', focused);

    return context;
  },
  __refreshSelected: function (){
    var context = this;
    var options = context.options;
    var dropdown = context.dropdown;
    var selectedClass = options.optionSelectedClass;
    var selectedIndex = context.element[0].selectedIndex;

    dropdown
      .find('.' + selectedClass)
      .removeClass(selectedClass);

    var selected = dropdown
      .find('[' + options.optionIndexAttr + '=' + selectedIndex + ']');

    selected.addClass(selectedClass);
    selected[0].scrollIntoView();

    return context;
  },
  __beauty: function (){
    var context = this;
    var element = context.element;

    if (!SelectBox.get(element)) {
      element.addClass('ui-beauty-select-hidden');

      var selectbox = $('
');

      context.titlebox = $('
');
      context.dropdown = $('
');

      selectbox
        .append(context.titlebox)
        .insertAfter(context.element);

      context.selectbox = selectbox;

      reference++;

      element.data('beauty-select', context);
    }

    return context.refresh();
  },
  focus: function (){
    this.element.trigger('focus');

    return this;
  },
  blur: function (){
    this.element.trigger('blur');

    return this;
  },
  enable: function (){
    this.element[0].disabled = false;

    return this.__refreshSelectbox();
  },
  disable: function (){
    this.element[0].disabled = true;

    return this.__refreshSelectbox();
  },
  refresh: function (){
    var context = this;

    context.__sizeSelectbox();
    context.__renderTitlebox();
    context.__renderDropdown();
    context.__sizeDropdown();

    return context.__refreshSelectbox();
  },
  select: function (index){
    this.element[0].selectedIndex = index;

    return this.__renderTitlebox();
  },
  open: function (){
    var context = this;

    if (context.opened) return context;

    context.opened = true;

    if (actived !== context) {
      actived.opened && actived.close();
      actived.__refreshSelectbox();
    }

    actived = context;

    context.selectbox.addClass('ui-beauty-select-opened');
    context.dropdown.appendTo(context.selectbox);
    context.__position();
    context.__refreshSelected();

    return context;
  },
  close: function (){
    var context = this;

    if (context.opened) {
      context.opened = false;

      context.dropdown.detach();
      context.selectbox.removeClass('ui-beauty-select-opened');
    }

    return context;
  },
  destroy: function (){
    var context = this;

    if (!context.destroyed) {
      var type = context.type;
      var element = context.element;
      var namespace = '.beauty-' + type;

      context.selectbox.off();
      context.element.off('keypress' + namespace);
      context.selectbox.remove();
      context.dropdown.remove();
      element.removeData('beauty-select');
      element.removeClass('ui-beauty-select-hidden');

      reference--;

      if (!reference) {
        doc.off('change' + namespace);
        doc.off('focusin' + namespace);
        doc.off('focusout' + namespace);
        doc.off('mousedown' + namespace);
        doc.off('keydown' + namespace);
        win.off('resize' + namespace);
      }

      context.destroyed = true;
    }
  }
};

$.fn.selectbox = function (){
  var elements = this;
  var method, options;
  var args = [].slice.call(arguments, 1);

  method = options = arguments[0];

  return elements.each(function (){
    var select = SelectBox.get(this);

    if (!select) {
      select = new SelectBox(this, options);
    }

    if ($.type(method) === 'string') {
      select[method] && select[method].apply(select, args);
    }
  });
};

module.exports = $;
<commit_msg>update files<commit_after>/*!
 * selectbox
 * Date: 2015/6/12
 * https://github.com/nuintun/beauty-form
 *
 * This is licensed under the MIT License (MIT).
 * For details, see: https://github.com/nuintun/beauty-form/blob/master/LICENSE
 */

'use strict';

require('./css/selectbox.css');

var $ = require('jquery');
var util = require('./util');

var timer;
var reference = 0;
var actived = null;
var win = $(window);
var doc = $(document);

/**
 * compile
 * @param context
 * @param template
 * @returns {string}
 */
function compile(context, template){
  var args = [].slice.call(arguments, 2);
  var html = template.apply(context, args);

  if ($.type(html) === 'string') {
    return html;
  } else {
    throw new TypeError('Render function must return a string.');
  }
}

function SelectBox(element, options){
  options = $.extend({
    title: function (element, text){
      return ''
        + '
'
        + text + '
';
    },
    dropdown: function (element, options){
      return '
' + options + '
';
    },
    optgroup: function (element, label){
      return '
' + label + '
';
    },
    option: function (element, option){
      return '
' + option.text + '
';
    },
    optionIndexAttr: 'data-option',
    optionSelectedClass: 'ui-beauty-select-option-selected',
    optionDisabledClass: 'ui-beauty-select-option-disabled'
  }, options);

  $.each(['title', 'dropdown', 'optgroup', 'option'], function (index, prop){
    if ($.type(options[prop]) !== 'function') {
      throw new TypeError('Options.' + prop + ' must be a function.');
    }
  });

  var context = this;

  context.type = 'select';
  context.opened = false;
  context.destroyed = false;
  context.element = $(element);
  context.options = options;

  context.__init();
}

/**
 * get
 * @param element
 * @returns {*}
 */
SelectBox.get = function (element){
  element = $(element);

  return element.data('beauty-select');
};

SelectBox.prototype = {
  __init: function (){
    var context = this;
    var type = context.type;
    var options = context.options;
    var namespace = '.beauty-' + type;

    actived = context;

    if (!reference) {
      var selector = 'select';

      doc.on('change' + namespace, selector, function (){
        var selectbox = SelectBox.get(this);

        if (selectbox) {
          selectbox.__renderTitlebox();
          selectbox.opened && selectbox.__refreshSelected();
        }
      });

      doc.on('focusin' + namespace + ' focusout' + namespace, selector, function (){
        var selectbox = SelectBox.get(this);

        selectbox && selectbox.__refreshSelectbox();
      });

      doc.on('mousedown' + namespace, function (e){
        var target = e.target;
        var selectbox = actived.selectbox[0];

        if (target !== selectbox && !$.contains(selectbox, target) && actived.opened) {
          actived.close();
          actived.__refreshSelectbox();
        }
      });

      doc.on('keydown' + namespace, function (e){
        if (e.which === 9 || e.which === 27) {
          actived.opened && actived.close();
          actived.__refreshSelectbox();
        }
      });

      win.on('resize' + namespace, function (){
        clearTimeout(timer);

        timer = setTimeout(function (){
          actived.opened && actived.__position();
        }, 0);
      });
    }

    context.__beauty();

    context.element.on('keypress' + namespace, function (e){
      if (e.which === 13) {
        e.preventDefault();

        if (context.opened) {
          context.close();
        } else {
          context.open();
        }
      }
    });

    context.selectbox.on('mousedown' + namespace, function (e){
      var select = context.element;

      if (select[0].disabled) return;

      e.preventDefault();

      context.focus();

      if (context.opened) {
        var target = e.target;
        var dropdown = context.dropdown[0];

        if (dropdown !== target && !$.contains(dropdown, target)) {
          context.close();
        }
      } else {
        context.open();
      }
    });

    context.selectbox.on('click' + namespace, '[' + options.optionIndexAttr + ']', function (e){
      e.preventDefault();

      var option = $(this);

      if (option.hasClass(options.optionDisabledClass)) return;

      context.select(option.attr(options.optionIndexAttr));
      context.close();
    });

    return context;
  },
  __sizeSelectbox: function (){
    var element = this.element;
    var selectbox = this.selectbox;
    var width = element.outerWidth() - selectbox.outerWidth() + selectbox.width();
    var height = element.outerHeight() - selectbox.outerHeight() + selectbox.height();

    selectbox.width(width);
    selectbox.height(height);

    return this;
  },
  __sizeDropdown: function (){
    var context = this;

    if (!context.opened) {
      context.dropdown.css('visibility', 'hidden');
      context.dropdown.appendTo(context.selectbox);
    }

    var element = context.element;
    var selectbox = context.selectbox;
    var dropdown = context.dropdown;
    var originWidth = element.outerWidth();

    element.width('auto');
    dropdown.width('auto');

    var adaptiveWidth = element.outerWidth();

    element.outerWidth(originWidth);

    var size = {
      selectbox: {
        outerWidth: selectbox.outerWidth()
      },
      dropdown: {
        width: dropdown.width(),
        outerWidth: dropdown.outerWidth()
      }
    };

    dropdown.width(Math.max(
      size.selectbox.outerWidth - size.dropdown.outerWidth + size.dropdown.width,
      adaptiveWidth - size.dropdown.outerWidth + size.dropdown.width
    ));

    if (!context.opened) {
      context.dropdown.css('visibility', 'visible');
      context.dropdown.detach();
    }

    return context;
  },
  __position: function (){
    var context = this;

    if (context.opened) {
      var selectbox = context.selectbox;
      var dropdown = context.dropdown;
      var scrollTop = win.scrollTop();
      var offset = selectbox.offset();

      var size = {
        window: { height: win.height() },
        selectbox: { outerHeight: selectbox.outerHeight() },
        dropdown: { outerHeight: dropdown.outerHeight() }
      };

      var top = offset.top - scrollTop;
      var bottom = size.window.height - top - size.selectbox.outerHeight;
      var position = top > bottom ? 'top' : 'bottom';

      dropdown
        .removeClass('ui-beauty-select-dropdown-' + (position === 'top' ? 'bottom' : 'top'))
        .addClass('ui-beauty-select-dropdown-' + position);

      dropdown.css({
        top: position === 'bottom' ? size.selectbox.outerHeight : -size.dropdown.outerHeight
      });
    }

    return context;
  },
  __renderTitlebox: function (){
    var context = this;
    var element = context.element[0];
    var selected = $(element.options[element.selectedIndex]);

    context.titlebox.html(compile(
      context,
      context.options.title,
      selected,
      selected.html()
    ));

    return context;
  },
  __renderDropdown: function (){
    var index = 0;
    var dropdown = '';
    var context = this;
    var options = context.options;
    var selectedIndex = context.element[0].selectedIndex;

    function option(element, group){
      var selected = index === selectedIndex;
      var option = {
        group: group,
        index: index++,
        text: element.html(),
        indexAttr: options.optionIndexAttr,
        className: element[0].disabled
          ? options.optionDisabledClass
          : ( selected ? options.optionSelectedClass : '')
      };

      dropdown += compile(
        context,
        options.option,
        element,
        option
      );
    }

    function optgroup(element){
      dropdown += compile(
        context,
        options.optgroup,
        element,
        element.attr('label')
      );
    }

    var items = context.element.children();

    items.each(function (){
      var element = $(this);

      switch (this.nodeName.toLowerCase()) {
        case 'option':
          option(element);
          break;
        case 'optgroup':
          optgroup(element);
          element.children().each(function (){
            option($(this), element);
          });
          break;
      }
    });

    context.dropdown.html(compile(
      context,
      options.dropdown,
      items,
      dropdown
    ));

    return context;
  },
  __refreshSelectbox: function (){
    var context = this;
    var element = context.element[0];
    var selectbox = context.selectbox;
    var focused = util.activeElement();

    focused = context.opened
      || focused === element
      || focused === selectbox[0]
      || $.contains(selectbox[0], focused);

    selectbox
      .toggleClass('ui-beauty-select-disabled', element.disabled)
      .toggleClass('ui-beauty-select-focus', focused);

    return context;
  },
  __refreshSelected: function (){
    var context = this;
    var options = context.options;
    var dropdown = context.dropdown;
    var selectedClass = options.optionSelectedClass;
    var selectedIndex = context.element[0].selectedIndex;

    dropdown
      .find('.' + selectedClass)
      .removeClass(selectedClass);

    var selected = dropdown
      .find('[' + options.optionIndexAttr + '=' + selectedIndex + ']');

    selected.addClass(selectedClass);
    selected[0].scrollIntoView();

    return context;
  },
  __beauty: function (){
    var context = this;
    var element = context.element;

    if (!SelectBox.get(element)) {
      element.addClass('ui-beauty-select-hidden');

      var selectbox = $('
');

      context.titlebox = $('
');
      context.dropdown = $('
');

      selectbox
        .append(context.titlebox)
        .insertAfter(context.element);

      context.selectbox = selectbox;

      reference++;

      element.data('beauty-select', context);
    }

    return context.refresh();
  },
  focus: function (){
    this.element.trigger('focus');

    return this;
  },
  blur: function (){
    this.element.trigger('blur');

    return this;
  },
  enable: function (){
    this.element[0].disabled = false;

    return this.__refreshSelectbox();
  },
  disable: function (){
    this.element[0].disabled = true;

    return this.__refreshSelectbox();
  },
  refresh: function (){
    var context = this;

    context.__sizeSelectbox();
    context.__renderTitlebox();
    context.__renderDropdown();
    context.__sizeDropdown();

    return context.__refreshSelectbox();
  },
  select: function (index){
    this.element[0].selectedIndex = index;

    return this.__renderTitlebox();
  },
  open: function (){
    var context = this;

    if (context.opened) return context;

    context.opened = true;

    if (actived !== context) {
      actived.opened && actived.close();
      actived.__refreshSelectbox();
    }

    actived = context;

    context.selectbox.addClass('ui-beauty-select-opened');
    context.dropdown.appendTo(context.selectbox);
    context.__position();
    context.__refreshSelected();

    return context;
  },
  close: function (){
    var context = this;

    if (context.opened) {
      context.opened = false;

      context.dropdown.detach();
      context.selectbox.removeClass('ui-beauty-select-opened');
    }

    return context;
  },
  destroy: function (){
    var context = this;

    if (!context.destroyed) {
      var type = context.type;
      var element = context.element;
      var namespace = '.beauty-' + type;

      context.selectbox.off();
      context.element.off('keypress' + namespace);
      context.selectbox.remove();
      context.dropdown.remove();
      element.removeData('beauty-select');
      element.removeClass('ui-beauty-select-hidden');

      reference--;

      if (!reference) {
        doc.off('change' + namespace);
        doc.off('focusin' + namespace);
        doc.off('focusout' + namespace);
        doc.off('mousedown' + namespace);
        doc.off('keydown' + namespace);
        win.off('resize' + namespace);
      }

      context.destroyed = true;
    }
  }
};

$.fn.selectbox = function (){
  var elements = this;
  var method, options;
  var args = [].slice.call(arguments, 1);

  method = options = arguments[0];

  return elements.each(function (){
    var select = SelectBox.get(this);

    if (!select) {
      select = new SelectBox(this, options);
    }

    if ($.type(method) === 'string') {
      select[method] && select[method].apply(select, args);
    }
  });
};

module.exports = $;
<|endoftext|>


<commit_before>import Texture from './gl/texture';
import WorkerBroker from './utils/worker_broker';

import log from 'loglevel';

export default class FeatureSelection {

    constructor(gl, workers) {
        this.gl = gl;
        this.workers = workers; // pool of workers to request feature look-ups from, keyed by id
        this.init();
    }

    init() {
        // Selection state tracking
        this.requests = {}; // pending selection requests
        this.feature = null; // currently selected feature
        this.read_delay = 5; // delay time from selection render to framebuffer sample, to avoid CPU/GPU sync lock
        this.read_delay_timer = null; // current timer (setTimeout) for delayed selection reads

        this.pixel = new Uint8Array(4);
        this.pixel32 = new Float32Array(this.pixel.buffer);

        // Frame buffer for selection
        // TODO: initiate lazily in case we don't need to do any selection
        this.fbo = this.gl.createFramebuffer();
        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
        this.fbo_size = { width: 256, height: 256 }; // TODO: make configurable / adaptive based on canvas size
        this.fbo_size.aspect = this.fbo_size.width / this.fbo_size.height;

        // Texture for the FBO color attachment
        var fbo_texture = Texture.create( this.gl, 'selection_fbo', { filtering: 'nearest' });
        fbo_texture.setData(this.fbo_size.width, this.fbo_size.height, null, { filtering: 'nearest' });
        this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, fbo_texture.texture, 0);

        // Renderbuffer for the FBO depth attachment
        var fbo_depth_rb = this.gl.createRenderbuffer();
        this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, fbo_depth_rb);
        this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, this.fbo_size.width, this.fbo_size.height);
        this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, fbo_depth_rb);

        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
    }

    destroy() {
        if (this.gl && this.fbo) {
            this.gl.deleteFramebuffer(this.fbo);
            this.fbo = null;
            this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
        }

        // TODO: free texture?
    }

    bind() {
        // Switch to FBO
        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
        this.gl.viewport(0, 0, this.fbo_size.width, this.fbo_size.height);
    }

    // Request feature selection
    // Runs asynchronously, schedules selection buffer to be updated
    getFeatureAt(point) {
        return new Promise((resolve, reject) => {
            // Queue requests for feature selection, and they will be picked up by the render loop
            this.selection_request_id = (this.selection_request_id + 1) || 0;
            this.requests[this.selection_request_id] = {
                type: 'point',
                id: this.selection_request_id,
                point,
                resolve,
                reject
            };
        });
    }

    // Any pending selection requests
    pendingRequests() {
        return this.requests;
    }

    clearPendingRequests() {
        for (var r in this.requests) {
            var request = this.requests[r];

            // This request was already sent to the worker, we're just awaiting its reply
            if (request.sent) {
                continue;
            }

            // Reject request since it will never be fulfilled
            // TODO: pass a reason for rejection?
            request.reject({ request });
            delete this.requests[r];
        }
    }

    // Read pending results from the selection buffer. Called after rendering to selection buffer.
    read() {
        // Delay reading the pixel result from the selection buffer to avoid CPU/GPU sync lock.
        // Calling readPixels synchronously caused a massive performance hit, presumably since it
        // forced this function to wait for the GPU to finish rendering and retrieve the texture contents.
        if (this.read_delay_timer != null) {
            clearTimeout(this.read_delay_timer);
        }
        this.read_delay_timer = setTimeout(() => {
            var gl = this.gl;

            gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);

            for (var r in this.requests) {
                var request = this.requests[r];

                // This request was already sent to the worker, we're just awaiting its reply
                if (request.sent) {
                    continue;
                }

                // TODO: support other selection types, such as features within a box
                if (request.type !== 'point') {
                    continue;
                }

                // Check selection map against FBO
                gl.readPixels(
                    Math.floor(request.point.x * this.fbo_size.width),
                    Math.floor((1 - request.point.y) * this.fbo_size.height),
                    1, 1, gl.RGBA, gl.UNSIGNED_BYTE, this.pixel);
                var feature_key = (this.pixel[0] + (this.pixel[1] << 8) + (this.pixel[2] << 16) + (this.pixel[3] << 24)) >>> 0;

                // If feature found, ask appropriate web worker to lookup feature
                var worker_id = this.pixel[3];
                if (worker_id !== 255) { // 255 indicates an empty selection buffer pixel
                    if (this.workers[worker_id] != null) {
                        WorkerBroker.postMessage(
                            this.workers[worker_id],
                            'self.getFeatureSelection',
                            { id: request.id, key: feature_key })
                        .then(message => {
                            this.finishRead(message);
                        });
                    }
                }
                // No feature found, but still need to resolve promise
                else {
                    this.finishRead({ id: request.id, feature: null });
                }

                request.sent = true;
            }

            gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        }, this.read_delay);
    }

    // Called on main thread when a web worker finds a feature in the selection buffer
    finishRead (message) {
        var request = this.requests[message.id];
        if (!request) {
            log.error("FeatureSelection.finishRead(): could not find message", message);
            return; // request was cleared before it returned
        }

        var feature = message.feature;
        var changed = false;
        if ((feature != null && this.feature == null) ||
            (feature == null && this.feature != null) ||
            (feature != null && this.feature != null &&
                JSON.stringify(feature) !== JSON.stringify(this.feature))) {
            changed = true;
        }

        this.feature = feature; // store the most recently selected feature

        // Resolve the request
        request.resolve({ feature, changed, request });
        delete this.requests[message.id]; // done processing this request
    }

    // Selection map generation
    // Each worker will create its own independent, 'local' selection map

    // Create a unique 32-bit color to identify a feature
    // Workers independently create/modify selection colors in their own threads, but we also
    // need the main thread to know where each feature color originated. To accomplish this,
    // we partition the map by setting the 4th component (alpha channel) to the worker's id.
    static makeEntry(tile) {
        // 32-bit color key
        this.map_entry++;
        var ir = this.map_entry & 255;
        var ig = (this.map_entry >> 8) & 255;
        var ib = (this.map_entry >> 16) & 255;
        var ia = this.map_prefix;
        var r = ir / 255;
        var g = ig / 255;
        var b = ib / 255;
        var a = ia / 255;
        var key = (ir + (ig << 8) + (ib << 16) + (ia << 24)) >>> 0; // need unsigned right shift to convert to positive #

        this.map[key] = {
            color: [r, g, b, a],
        };
        this.map_size++;

        this.tiles[tile.key] = this.tiles[tile.key] || [];
        this.tiles[tile.key].push(key);

        return this.map[key];
    }

    static makeColor(feature, tile, context) {
        var selector = this.makeEntry(tile);
        selector.feature = {
            id: feature.id,
            properties: feature.properties,
            tile: tile.key,
            layers: context.layers
        };

        return selector.color;
    }

    static reset() {
        this.tiles = {};
        this.map = {};
        this.map_size = 0;
        this.map_entry = 0;
    }

    static clearTile(key) {
        if (Array.isArray(this.tiles[key])) {
            this.tiles[key].forEach(k => delete this.map[k]);
            this.map_size -= this.tiles[key].length;
            delete this.tiles[key];
        }
    }

    static getMapSize() {
        return this.map_size;
    }

    static setPrefix(prefix) {
        this.map_prefix = prefix;
    }

}

// Static properties
FeatureSelection.map = {};   // this will be unique per module instance (so unique per worker)
FeatureSelection.tiles = {}; // selection keys, by tile
FeatureSelection.map_size = 0;
FeatureSelection.map_entry = 0;
FeatureSelection.map_prefix = 0; // set by worker to worker id #
FeatureSelection.defaultColor = [0, 0, 0, 1];
<commit_msg>include additional tile info in feature selection object<commit_after>import Texture from './gl/texture';
import WorkerBroker from './utils/worker_broker';

import log from 'loglevel';

export default class FeatureSelection {

    constructor(gl, workers) {
        this.gl = gl;
        this.workers = workers; // pool of workers to request feature look-ups from, keyed by id
        this.init();
    }

    init() {
        // Selection state tracking
        this.requests = {}; // pending selection requests
        this.feature = null; // currently selected feature
        this.read_delay = 5; // delay time from selection render to framebuffer sample, to avoid CPU/GPU sync lock
        this.read_delay_timer = null; // current timer (setTimeout) for delayed selection reads

        this.pixel = new Uint8Array(4);
        this.pixel32 = new Float32Array(this.pixel.buffer);

        // Frame buffer for selection
        // TODO: initiate lazily in case we don't need to do any selection
        this.fbo = this.gl.createFramebuffer();
        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
        this.fbo_size = { width: 256, height: 256 }; // TODO: make configurable / adaptive based on canvas size
        this.fbo_size.aspect = this.fbo_size.width / this.fbo_size.height;

        // Texture for the FBO color attachment
        var fbo_texture = Texture.create( this.gl, 'selection_fbo', { filtering: 'nearest' });
        fbo_texture.setData(this.fbo_size.width, this.fbo_size.height, null, { filtering: 'nearest' });
        this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, fbo_texture.texture, 0);

        // Renderbuffer for the FBO depth attachment
        var fbo_depth_rb = this.gl.createRenderbuffer();
        this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, fbo_depth_rb);
        this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, this.fbo_size.width, this.fbo_size.height);
        this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, fbo_depth_rb);

        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
    }

    destroy() {
        if (this.gl && this.fbo) {
            this.gl.deleteFramebuffer(this.fbo);
            this.fbo = null;
            this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
        }

        // TODO: free texture?
    }

    bind() {
        // Switch to FBO
        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
        this.gl.viewport(0, 0, this.fbo_size.width, this.fbo_size.height);
    }

    // Request feature selection
    // Runs asynchronously, schedules selection buffer to be updated
    getFeatureAt(point) {
        return new Promise((resolve, reject) => {
            // Queue requests for feature selection, and they will be picked up by the render loop
            this.selection_request_id = (this.selection_request_id + 1) || 0;
            this.requests[this.selection_request_id] = {
                type: 'point',
                id: this.selection_request_id,
                point,
                resolve,
                reject
            };
        });
    }

    // Any pending selection requests
    pendingRequests() {
        return this.requests;
    }

    clearPendingRequests() {
        for (var r in this.requests) {
            var request = this.requests[r];

            // This request was already sent to the worker, we're just awaiting its reply
            if (request.sent) {
                continue;
            }

            // Reject request since it will never be fulfilled
            // TODO: pass a reason for rejection?
            request.reject({ request });
            delete this.requests[r];
        }
    }

    // Read pending results from the selection buffer. Called after rendering to selection buffer.
    read() {
        // Delay reading the pixel result from the selection buffer to avoid CPU/GPU sync lock.
        // Calling readPixels synchronously caused a massive performance hit, presumably since it
        // forced this function to wait for the GPU to finish rendering and retrieve the texture contents.
        if (this.read_delay_timer != null) {
            clearTimeout(this.read_delay_timer);
        }
        this.read_delay_timer = setTimeout(() => {
            var gl = this.gl;

            gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);

            for (var r in this.requests) {
                var request = this.requests[r];

                // This request was already sent to the worker, we're just awaiting its reply
                if (request.sent) {
                    continue;
                }

                // TODO: support other selection types, such as features within a box
                if (request.type !== 'point') {
                    continue;
                }

                // Check selection map against FBO
                gl.readPixels(
                    Math.floor(request.point.x * this.fbo_size.width),
                    Math.floor((1 - request.point.y) * this.fbo_size.height),
                    1, 1, gl.RGBA, gl.UNSIGNED_BYTE, this.pixel);
                var feature_key = (this.pixel[0] + (this.pixel[1] << 8) + (this.pixel[2] << 16) + (this.pixel[3] << 24)) >>> 0;

                // If feature found, ask appropriate web worker to lookup feature
                var worker_id = this.pixel[3];
                if (worker_id !== 255) { // 255 indicates an empty selection buffer pixel
                    if (this.workers[worker_id] != null) {
                        WorkerBroker.postMessage(
                            this.workers[worker_id],
                            'self.getFeatureSelection',
                            { id: request.id, key: feature_key })
                        .then(message => {
                            this.finishRead(message);
                        });
                    }
                }
                // No feature found, but still need to resolve promise
                else {
                    this.finishRead({ id: request.id, feature: null });
                }

                request.sent = true;
            }

            gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        }, this.read_delay);
    }

    // Called on main thread when a web worker finds a feature in the selection buffer
    finishRead (message) {
        var request = this.requests[message.id];
        if (!request) {
            log.error("FeatureSelection.finishRead(): could not find message", message);
            return; // request was cleared before it returned
        }

        var feature = message.feature;
        var changed = false;
        if ((feature != null && this.feature == null) ||
            (feature == null && this.feature != null) ||
            (feature != null && this.feature != null &&
                JSON.stringify(feature) !== JSON.stringify(this.feature))) {
            changed = true;
        }

        this.feature = feature; // store the most recently selected feature

        // Resolve the request
        request.resolve({ feature, changed, request });
        delete this.requests[message.id]; // done processing this request
    }

    // Selection map generation
    // Each worker will create its own independent, 'local' selection map

    // Create a unique 32-bit color to identify a feature
    // Workers independently create/modify selection colors in their own threads, but we also
    // need the main thread to know where each feature color originated. To accomplish this,
    // we partition the map by setting the 4th component (alpha channel) to the worker's id.
    static makeEntry(tile) {
        // 32-bit color key
        this.map_entry++;
        var ir = this.map_entry & 255;
        var ig = (this.map_entry >> 8) & 255;
        var ib = (this.map_entry >> 16) & 255;
        var ia = this.map_prefix;
        var r = ir / 255;
        var g = ig / 255;
        var b = ib / 255;
        var a = ia / 255;
        var key = (ir + (ig << 8) + (ib << 16) + (ia << 24)) >>> 0; // need unsigned right shift to convert to positive #

        this.map[key] = {
            color: [r, g, b, a],
        };
        this.map_size++;

        this.tiles[tile.key] = this.tiles[tile.key] || [];
        this.tiles[tile.key].push(key);

        return this.map[key];
    }

    static makeColor(feature, tile, context) {
        var selector = this.makeEntry(tile);
        selector.feature = {
            id: feature.id,
            properties: feature.properties,
            layers: context.layers,
            tile: {
                key: tile.key,
                coords: tile.coords,
                style_zoom: tile.style_zoom,
                source: tile.source,
                generation: tile.generation

            }
        };

        return selector.color;
    }

    static reset() {
        this.tiles = {};
        this.map = {};
        this.map_size = 0;
        this.map_entry = 0;
    }

    static clearTile(key) {
        if (Array.isArray(this.tiles[key])) {
            this.tiles[key].forEach(k => delete this.map[k]);
            this.map_size -= this.tiles[key].length;
            delete this.tiles[key];
        }
    }

    static getMapSize() {
        return this.map_size;
    }

    static setPrefix(prefix) {
        this.map_prefix = prefix;
    }

}

// Static properties
FeatureSelection.map = {};   // this will be unique per module instance (so unique per worker)
FeatureSelection.tiles = {}; // selection keys, by tile
FeatureSelection.map_size = 0;
FeatureSelection.map_entry = 0;
FeatureSelection.map_prefix = 0; // set by worker to worker id #
FeatureSelection.defaultColor = [0, 0, 0, 1];
<|endoftext|>


<commit_before>var mysql = require('mysql')
var Q = require('Q')

/*
 * Todo:
 * Add cache, I love redis
 */

var DB = (function() {
	function DB(pool) {
		this.pool = pool

		return this
	}

	DB.prototype.getConnection = function() {
		var defer = Q.defer()

		this.pool.getConnection(function(err, conn) {
			if(err) {
				return defer.reject(err)
			}

			defer.resolve(conn)
		})

		return defer.promise
	}

	// This passes arguments directly to mysql.connection.query
	DB.prototype.query = function(sql, vals) {
		var pool = this.pool
		return this.getConnection().then(function(conn) {
			var defer = Q.defer()

			conn.query(sql, vals, function(err, rows) {
				// For some reason the program hangs if I dont destroy the connections, which
				// kind of defeats the purpous.
				conn.destroy()

				if(err) {
					return defer.reject(err)
				}

				defer.resolve(rows)
			})

			return defer.promise
		})
	}

	DB.prototype.getChannels = function() {
		//return this.query("SELECT Channels.* FROM Channels;")
		return this.query("SELECT Channels.*, COUNT(Items.item_id) as unread_count FROM Channels LEFT JOIN Items USING (channel_id) WHERE Items.read = 0 GROUP BY Channels.channel_id");
	}

	DB.prototype.getFeed = function(id) {
		return this.query("SELECT * FROM Items WHERE channel_id = ? AND `read` = 0 ORDER BY published", [id])
	}

	DB.prototype.markRead = function(id) {
		return this.query("UPDATE Items SET `read` = 1 WHERE item_id = ?", [id])
	}

	DB.prototype.getFeedItem = function(id) {
		return this.query("SELECT * FROM Items WHERE item_id = ?", [id])
	}

	return DB
})()

module.exports = function(config) {
	var pool = mysql.createPool(config)

	var db = new DB(pool)

	return db
}
<commit_msg>Add db API to get group ids and to insert a group and return its ID or return the id for a given group name.<commit_after>var mysql = require('mysql')
var Q = require('Q')

/*
 * Todo:
 * Add cache, I love redis
 */

var DB = (function() {
	function DB(pool) {
		this.pool = pool

		return this
	}

	DB.prototype.getConnection = function() {
		var defer = Q.defer()

		this.pool.getConnection(function(err, conn) {
			if(err) {
				return defer.reject(err)
			}

			defer.resolve(conn)
		})

		return defer.promise
	}

	// This passes arguments directly to mysql.connection.query
	DB.prototype.query = function(sql, vals) {
		var pool = this.pool
		return this.getConnection().then(function(conn) {
			var defer = Q.defer()

			conn.query(sql, vals, function(err, rows) {
				// For some reason the program hangs if I dont destroy the connections, which
				// kind of defeats the purpous.
				conn.destroy()

				if(err) {
					return defer.reject(err)
				}

				defer.resolve(rows)
			})

			return defer.promise
		})
	}

	DB.prototype.getChannels = function() {
		//return this.query("SELECT Channels.* FROM Channels;")
		return this.query("SELECT Channels.*, COUNT(Items.item_id) as unread_count FROM Channels LEFT JOIN Items USING (channel_id) WHERE Items.read = 0 GROUP BY Channels.channel_id");
	}

	DB.prototype.getFeed = function(id) {
		return this.query("SELECT * FROM Items WHERE channel_id = ? AND `read` = 0 ORDER BY published", [id])
	}

	DB.prototype.markRead = function(id) {
		return this.query("UPDATE Items SET `read` = 1 WHERE item_id = ?", [id])
	}

	DB.prototype.getFeedItem = function(id) {
		return this.query("SELECT * FROM Items WHERE item_id = ?", [id])
	}

	DB.prototype.getGroupId = function(name) {
		return this.query("SELECT group_id FROM Groups WHERE name = ?", [name])
	}

	DB.prototype.insertAndGetGroup = function(name) {
		var this$ = this
		var defer = Q.defer()

		this$.query("INSERT INTO Groups SET name = ?", [name]).then(function(res) {
			defer.resolve(res.insertId)
		}, function(err) {
			if(err.code !== 'ER_DUP_ENTRY') {
				return defer.reject(err)
			}

			this$.query("SELECT group_id FROM Groups WHERE name = ?", [name]).then(function(res) {
				defer.resolve(res[0].group_id)
			}, function(err) {
				defer.reject(err)
			})
		}).catch(function(err) {
			console.log(err.stack)
		})

		return defer.promise
	}

	return DB
})()

module.exports = function(config) {
	var pool = mysql.createPool(config)

	var db = new DB(pool)

	return db
}
<|endoftext|>


<commit_before>/* @flow */

import { CompositeDisposable } from "atom";
import React from "react";
import * as Immutable from "immutable";
import { observer } from "mobx-react";
import { action, observable } from "mobx";
import { Display } from "@nteract/display-area";

import { transforms, displayOrder } from "./transforms";
import Status from "./status";

import type { IObservableValue } from "mobx";
import type OutputStore from "./../../store/output";
import type Kernel from "./../../kernel";

type Props = {
  store: OutputStore,
  kernel: Kernel,
  destroy: Function,
  showResult: boolean
};

@observer
class ResultViewComponent extends React.Component {
  props: Props;
  el: ?HTMLElement;
  containerTooltip = new CompositeDisposable();
  buttonTooltip = new CompositeDisposable();
  expanded: IObservableValue<boolean> = observable(false);

  getAllText = () => {
    if (!this.el) return "";
    return this.el.innerText ? this.el.innerText.trim() : "";
  };

  copyToClipboard = () => {
    atom.clipboard.write(this.getAllText());
    atom.notifications.addSuccess("Copied to clipboard");
  };

  openInEditor = () => {
    atom.workspace.open().then(editor => editor.insertText(this.getAllText()));
  };

  addCopyTooltip = (element: ?HTMLElement, comp: atom$CompositeDisposable) => {
    if (!element || !comp.disposables || comp.disposables.size > 0) return;
    comp.add(
      atom.tooltips.add(element, {
        title: this.props.store.executionCount
          ? `Copy to clipboard (Out[${this.props.store.executionCount}])`
          : "Copy to clipboard"
      })
    );
  };

  addCopyButtonTooltip = (element: ?HTMLElement) => {
    this.addCopyTooltip(element, this.buttonTooltip);
  };

  onWheel = (element: HTMLElement) => {
    return (event: WheelEvent) => {
      const clientHeight = element.clientHeight;
      const scrollHeight = element.scrollHeight;
      const scrollTop = element.scrollTop;
      const atTop = scrollTop !== 0 && event.deltaY < 0;
      const atBottom =
        scrollTop !== scrollHeight - clientHeight && event.deltaY > 0;

      if (clientHeight < scrollHeight && (atTop || atBottom)) {
        event.stopPropagation();
      }
    };
  };

  @action toggleExpand = () => {
    this.expanded.set(!this.expanded.get());
  };

  render() {
    const { outputs, status, isPlain, position } = this.props.store;

    const inlineStyle = {
      marginLeft: `${position.lineLength + 1}ch`,
      marginTop: `-${position.lineHeight}px`
    };

    if (outputs.length === 0 || this.props.showResult === false) {
      const { executionState } = this.props.kernel;
      return (
        <Status
          status={
            executionState !== "busy" && status === "running" ? "error" : status
          }
          style={inlineStyle}
        />
      );
    }

    return (
      <div
        className={isPlain ? "inline-container" : "multiline-container"}
        onClick={isPlain ? this.copyToClipboard : false}
        style={
          isPlain
            ? inlineStyle
            : { maxWidth: `${position.editorWidth}ch`, margin: "0px" }
        }
      >
        <Display
          ref={ref => {
            if (!ref || !ref.el) return;
            this.el = ref.el;

            isPlain
              ? this.addCopyTooltip(ref.el, this.containerTooltip)
              : this.containerTooltip.dispose();

            // React's event handler doesn't properly handle event.stopPropagation() for
            // events outside the React context. Using proxy.el saves us a extra div.
            // We only need this in the text editor, therefore we check showStatus.
            if (!this.expanded.get() && !isPlain) {
              ref.el.addEventListener("wheel", this.onWheel(ref.el), {
                passive: true
              });
            }
          }}
          outputs={Immutable.List(outputs.peek())}
          displayOrder={displayOrder}
          transforms={transforms}
          theme="light"
          models={Immutable.Map()}
          expanded={this.expanded.get()}
        />
        {isPlain
          ? null
          :

}

    );
  }

  scrollToBottom() {
    if (
      !this.el ||
      this.expanded === true ||
      this.props.store.isPlain === true ||
      atom.config.get(`Hydrogen.autoScroll`) === false
    )
      return;
    const scrollHeight = this.el.scrollHeight;
    const height = this.el.clientHeight;
    const maxScrollTop = scrollHeight - height;
    this.el.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
  }

  componentDidUpdate() {
    this.scrollToBottom();
  }

  componentDidMount() {
    this.scrollToBottom();
  }

  componentWillUnmount() {
    this.containerTooltip.dispose();
    this.buttonTooltip.dispose();
  }
}

export default ResultViewComponent;
<commit_msg>Hide expand button (#859)<commit_after>/* @flow */

import { CompositeDisposable } from "atom";
import React from "react";
import * as Immutable from "immutable";
import { observer } from "mobx-react";
import { action, observable } from "mobx";
import Display, { DEFAULT_SCROLL_HEIGHT } from "@nteract/display-area/lib/display";
import { transforms, displayOrder } from "./transforms";
import Status from "./status";

import type { IObservableValue } from "mobx";
import type OutputStore from "./../../store/output";
import type Kernel from "./../../kernel";

type Props = {
  store: OutputStore,
  kernel: Kernel,
  destroy: Function,
  showResult: boolean
};

@observer
class ResultViewComponent extends React.Component {
  props: Props;
  el: ?HTMLElement;
  containerTooltip = new CompositeDisposable();
  buttonTooltip = new CompositeDisposable();
  expanded: IObservableValue<boolean> = observable(false);

  getAllText = () => {
    if (!this.el) return "";
    return this.el.innerText ? this.el.innerText.trim() : "";
  };

  copyToClipboard = () => {
    atom.clipboard.write(this.getAllText());
    atom.notifications.addSuccess("Copied to clipboard");
  };

  openInEditor = () => {
    atom.workspace.open().then(editor => editor.insertText(this.getAllText()));
  };

  addCopyTooltip = (element: ?HTMLElement, comp: atom$CompositeDisposable) => {
    if (!element || !comp.disposables || comp.disposables.size > 0) return;
    comp.add(
      atom.tooltips.add(element, {
        title: this.props.store.executionCount
          ? `Copy to clipboard (Out[${this.props.store.executionCount}])`
          : "Copy to clipboard"
      })
    );
  };

  addCopyButtonTooltip = (element: ?HTMLElement) => {
    this.addCopyTooltip(element, this.buttonTooltip);
  };

  onWheel = (element: HTMLElement) => {
    return (event: WheelEvent) => {
      const clientHeight = element.clientHeight;
      const scrollHeight = element.scrollHeight;
      const scrollTop = element.scrollTop;
      const atTop = scrollTop !== 0 && event.deltaY < 0;
      const atBottom =
        scrollTop !== scrollHeight - clientHeight && event.deltaY > 0;

      if (clientHeight < scrollHeight && (atTop || atBottom)) {
        event.stopPropagation();
      }
    };
  };

  @action toggleExpand = () => {
    this.expanded.set(!this.expanded.get());
  };

  render() {
    const { outputs, status, isPlain, position } = this.props.store;

    const inlineStyle = {
      marginLeft: `${position.lineLength + 1}ch`,
      marginTop: `-${position.lineHeight}px`
    };

    if (outputs.length === 0 || this.props.showResult === false) {
      const { executionState } = this.props.kernel;
      return (
        <Status
          status={
            executionState !== "busy" && status === "running" ? "error" : status
          }
          style={inlineStyle}
        />
      );
    }

    return (
      <div
        className={isPlain ? "inline-container" : "multiline-container"}
        onClick={isPlain ? this.copyToClipboard : false}
        style={
          isPlain
            ? inlineStyle
            : { maxWidth: `${position.editorWidth}ch`, margin: "0px" }
        }
      >
        <Display
          ref={ref => {
            if (!ref || !ref.el) return;
            this.el = ref.el;

            isPlain
              ? this.addCopyTooltip(ref.el, this.containerTooltip)
              : this.containerTooltip.dispose();

            // React's event handler doesn't properly handle event.stopPropagation() for
            // events outside the React context. Using proxy.el saves us a extra div.
            // We only need this in the text editor, therefore we check showStatus.
            if (!this.expanded.get() && !isPlain) {
              ref.el.addEventListener("wheel", this.onWheel(ref.el), {
                passive: true
              });
            }
          }}
          outputs={Immutable.List(outputs.peek())}
          displayOrder={displayOrder}
          transforms={transforms}
          theme="light"
          models={Immutable.Map()}
          expanded={this.expanded.get()}
        />
        {isPlain
          ? null
          :

              {this.el && this.el.scrollHeight > DEFAULT_SCROLL_HEIGHT
                ?

                : null}

}

    );
  }

  scrollToBottom() {
    if (
      !this.el ||
      this.expanded === true ||
      this.props.store.isPlain === true ||
      atom.config.get(`Hydrogen.autoScroll`) === false
    )
      return;
    const scrollHeight = this.el.scrollHeight;
    const height = this.el.clientHeight;
    const maxScrollTop = scrollHeight - height;
    this.el.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
  }

  componentDidUpdate() {
    this.scrollToBottom();
  }

  componentDidMount() {
    this.scrollToBottom();
  }

  componentWillUnmount() {
    this.containerTooltip.dispose();
    this.buttonTooltip.dispose();
  }
}

export default ResultViewComponent;
<|endoftext|>