<commit_before>(function($) {
    $(function() {
        // Menu toggling
        $('#menu-button').on('click', function() {
            $('#page').toggleClass('exposed');
        });

        function showNotification(notification) {
            $(notification).first().fadeIn(600, function() {
                $(this).delay(5000).fadeOut(600, function() {
                    var next = $(this).next();
                    $(this).remove();
                    showNotification(next);
                });
            });
        }

        $('#cancel-button').on('click', function() {
           if (!confirm(gettext('Are you sure you wish to cancel?'))) {
               return false;
           }
        });

        showNotification($('#notifications > li').fadeOut(0));

        $(document).on('click', '.overlay > header', function() {
            $(this).closest('.overlay').hide();
        });

        $(document).on('click', '.pulldown-menu .pulldown-handle', function() {
            $(this).closest('.pulldown-menu').toggleClass('open');
        });

        $(document).on('click', '[data-overlay]', function() {
            var overlay = $(this).data('overlay');
            if (overlay) {
                $('#' + overlay).show();
            }
        });

        $(document).on('click', '[data-show]', function() {
            $('#' + $(this).data('show')).css('display', '');
        });

        $(document).on('click', '[data-hide]', function() {
            $('#' + $(this).data('hide')).hide();
        });

        $(document).on('click', '[data-expand]', function() {
            $('#' + $(this).data('expand')).addClass('expanded');
        });

        $(document).on('click', '[data-submit]', function() {
            $('#' + $(this).data('submit')).submit();
        });

        $(document).on('click', '.collapsable .toggle', function() {
            $(this).closest('.collapsable').toggleClass('expanded');
        });

        // Swipeable lists
        $('.swipeable').each(function() {
            var width = 0;
            $(this).children('ul').find('li').each(function() {
                width += $(this).outerWidth() + parseInt($(this).css('marginLeft')) + parseInt($(this).css('marginRight'));
            });
            $(this).children('ul').css('width', width + 'px');
        });

        // iOS Standalone Web App Fix
        if (("standalone" in window.navigator) && window.navigator.standalone) {
            $(document).on('click', 'a', function(event) {
                var href = $(event.target).attr('href');

                if (href) {
                    event.preventDefault();
                    location.href = href;
                }
            });
        }

        if ($('body').is('.aaq')) {
            // Pre-populate form with user's system info
            new AAQSystemInfo($('#question-form'));
        }

        // iOS overscroll fix
        document.addEventListener('touchmove', function(e) {
            e.preventDefault();
        }, false);

        $('.scrollable').each(function() {
            var y = 0;
            var isTop = false;
            var isBottom = false;

            this.addEventListener('touchstart', function(e) {
                y = e.pageY;
                isTop = this.scrollTop <= 0;
                isBottom = this.scrollHeight - this.scrollTop <= this.clientHeight;
            }, true);

            this.addEventListener('touchmove', function(e) {
                var scrollUp = e.pageY > y;

                if ((scrollUp && !isTop) || (!scrollUp && !isBottom)) {
                    e.stopPropagation();
                } else {
                    e.preventDefault();
                }
            }, true);
        });
    });
})(jQuery)
<commit_msg>Remove iOS scroll fix JS<commit_after>(function($) {
    $(function() {
        // Menu toggling
        $('#menu-button').on('click', function() {
            $('#page').toggleClass('exposed');
        });

        function showNotification(notification) {
            $(notification).first().fadeIn(600, function() {
                $(this).delay(5000).fadeOut(600, function() {
                    var next = $(this).next();
                    $(this).remove();
                    showNotification(next);
                });
            });
        }

        $('#cancel-button').on('click', function() {
           if (!confirm(gettext('Are you sure you wish to cancel?'))) {
               return false;
           }
        });

        showNotification($('#notifications > li').fadeOut(0));

        $(document).on('click', '.overlay > header', function() {
            $(this).closest('.overlay').hide();
        });

        $(document).on('click', '.pulldown-menu .pulldown-handle', function() {
            $(this).closest('.pulldown-menu').toggleClass('open');
        });

        $(document).on('click', '[data-overlay]', function() {
            var overlay = $(this).data('overlay');
            if (overlay) {
                $('#' + overlay).show();
            }
        });

        $(document).on('click', '[data-show]', function() {
            $('#' + $(this).data('show')).css('display', '');
        });

        $(document).on('click', '[data-hide]', function() {
            $('#' + $(this).data('hide')).hide();
        });

        $(document).on('click', '[data-expand]', function() {
            $('#' + $(this).data('expand')).addClass('expanded');
        });

        $(document).on('click', '[data-submit]', function() {
            $('#' + $(this).data('submit')).submit();
        });

        $(document).on('click', '.collapsable .toggle', function() {
            $(this).closest('.collapsable').toggleClass('expanded');
        });

        // Swipeable lists
        $('.swipeable').each(function() {
            var width = 0;
            $(this).children('ul').find('li').each(function() {
                width += $(this).outerWidth() + parseInt($(this).css('marginLeft')) + parseInt($(this).css('marginRight'));
            });
            $(this).children('ul').css('width', width + 'px');
        });

        // iOS Standalone Web App Fix
        if (("standalone" in window.navigator) && window.navigator.standalone) {
            $(document).on('click', 'a', function(event) {
                var href = $(event.target).attr('href');

                if (href) {
                    event.preventDefault();
                    location.href = href;
                }
            });
        }

        if ($('body').is('.aaq')) {
            // Pre-populate form with user's system info
            new AAQSystemInfo($('#question-form'));
        }
    });
})(jQuery)
<|endoftext|>


<commit_before>

function standsViewModel () {
    var self = this;

    // display the form panel entirely
    self.showStandPanels = ko.observable();
    // display initial help
    self.showStandHelp = ko.observable(true);
    // display drawing help
    self.showDrawPanel = ko.observable(false);
    // display form
    self.showStandForm = ko.observable(false);
    // display list of stands
    self.showStandList = ko.observable(true);

    self.showNoStandHelp = ko.observable(true);

    self.standList = ko.observableArray();

    self.cancelManageStands = function () {
        self.showStandPanels(false);
        app.properties.viewModel.showPropertyPanels(true);
        app.property_layer.setOpacity(1);
        map.removeLayer(self.stand_layer);
        map.removeLayer(self.property_layer);
        app.drawFeature.featureAdded = app.properties.featureAdded;
        app.selectFeature.activate();
    }

    self.addStandStart = function () {
        app.drawFeature.activate();
        self.showStandHelp(false);
        self.showStandList(false);
        self.showDrawPanel(true);
    }

    self.addStandForm = function () {
        // get the form
        var formUrl = app.workspace["feature-classes"][0]["link-relations"]["create"]["uri-template"];
        // clean up and show the form
        $.get(formUrl, function (data) {
          var $form = $(data).find('form');
          $form.find('input:submit').remove();
          // app.cleanupForm($form);
          $('#stands-form-container').empty().append($form);
          self.showNoStandHelp(false);
          self.showStandForm(true);
          $form.find('input:visible:first').focus();
          $form.bind('submit', function (event) {
            event.preventDefault();
            //self.addFeature(self, event);
          });
        })
        // activate drawing
        // on save submit form and geometry
        // get the new feature and add it to observable array
    }

    self.updateStand = function (stand_id) {
      var updateUrl = '/features/generic-links/links/geojson/{uid}/'.replace('{uid}',stand_id);
      $.get(updateUrl, function (data) {
        self.stand_layer.addFeatures(app.geojson_format.read(data));
      });
    }

    self.associateStand = function (stand_id, property_id) {
        var url = "/features/forestproperty/{forestproperty_uid}/add/{stand_uid}";
        url = url.replace('{forestproperty_uid}', property_id);
        url = url.replace('{stand_uid}', stand_id);
        $.ajax({
            url: url,
            type: "POST",
            success: function (data) {
              self.updateStand(JSON.parse(data)["X-Madrona-Select"]);
              app.stands.viewModel.showStandForm(false);
              app.stands.viewModel.showStandList(true);
              app.new_features.removeAllFeatures();
            }
        })

    }

    self.saveStandForm = function (self, event) {
      var $dialog = $('#stands-form-container'),
        $form = $dialog.find('form'),
        actionUrl = $form.attr('action'),
        values = {}, error=false;
      $form.find('input,select').each(function () {
        var $input = $(this);
        if ($input.closest('.field').hasClass('required') && $input.attr('type') !== 'hidden' && ! $input.val()) {
          error = true;
          $input.closest('.control-group').addClass('error');
          $input.siblings('p.help-block').text('This field is required.');
        } else {
          values[$input.attr('name')] = $input.val();
        }
      });
      if (error) {
        return false;
      }
      values.geometry_final = values.geometry_orig = app.stands.geometry;
      $form.addClass('form-horizontal');
      $.ajax({
        url: actionUrl,
        type: "POST",
        data: values,
        success: function (data, textStatus, jqXHR) {
          // property is not defined if new
          self.associateStand(JSON.parse(data)["X-Madrona-Select"], self.property.uid());
        },
        error: function (jqXHR, textStatus, errorThrown) {
          self.cancelAddStand();
        }
      });
    };

    self.cancelAddStand = function () {
      self.showStandForm(false);
      self.showStandHelp(true);
      self.showStandList(true);
      self.showDrawPanel(false);
      app.new_features.removeAllFeatures()
      app.drawFeature.deactivate();
      self.showNoStandHelp(true);
    }

    self.initialize = function (property) {
      // bind the viewmodel
      ko.applyBindings(self, document.getElementById('stand-html'));

      // start the stand manager
      self.property_layer = new OpenLayers.Layer.Vector("Active Property",  {
                      renderers: app.renderer,
                      styleMap: new OpenLayers.StyleMap({
                              fillOpacity: 0,
                              strokeWidth: 2,
                              strokeOpacity: 1,
                              strokeColor: "#44ff00",
                          })
                  });
      map.addLayer(self.property_layer);
      //map.zoomToExtent(self.property_layer.getDataExtent());
      app.stands.featureAdded = function(feature) {
        app.drawFeature.deactivate();
        self.showDrawPanel(false);
        app.stands.geometry = feature.geometry.toString();
        self.addStandForm();
        //app.saveFeature(feature);
      };

      self.stand_layer = new OpenLayers.Layer.Vector("Stands",  {
                      renderers: app.renderer,
                      styleMap:  new OpenLayers.StyleMap({
                          fillOpacity: .3,
                          fillColor: "brown",
                          strokeWidth: 1,
                          strokeOpacity: 1,
                          strokeColor: "brown",
                      })
                  });
      map.addLayer(self.stand_layer);
      self.stand_layer.events.on({
        'featureselected': function (feature) {
        },
        'featureadded': function (feature) {
          var featureViewModel = ko.mapping.fromJS(feature.feature.data);
          // save a reference to the feature
          featureViewModel.feature = feature.feature;
          // add it to the viewmodel
          self.standList.unshift(featureViewModel);
        },
        'featuresadded': function (data) {
        }
      });
      map.addLayer(self.stand_layer);

      self.snapper = new OpenLayers.Control.Snapping({
         layer: app.new_features,
         targets: [self.property_layer, self.stand_layer],
         greedy: false
      });
      self.snapper.activate();
      self.loadStands(property);
    }

    self.reloadStands = function (property) {
      self.stand_layer.removeAllFeatures();
      self.standList.removeAll();
      self.property_layer.removeAllFeatures();
      map.addLayer(self.property_layer);
      map.addLayer(self.stand_layer);
      self.loadStands(property);
      self.showStandList(true);
    }

    self.loadStands = function (property) {
      self.property = property;
      app.drawFeature.featureAdded = app.stands.featureAdded;
      app.selectFeature.deactivate();
      self.property_layer.addFeatures(property.feature.clone());
      $.get('/trees/property_stand_list/{property_id}/'.replace('{property_id}', property.uid()), function (data) {
          if (data.features.length) {
              self.stand_layer.addFeatures(app.geojson_format.read(data));
              self.showStandHelp(false);
          } else {
            self.showStandHelp(true);
          }
      });

    }

    return self;
}

<commit_msg>style tweaks<commit_after>

function standsViewModel () {
    var self = this;

    // display the form panel entirely
    self.showStandPanels = ko.observable();
    // display initial help
    self.showStandHelp = ko.observable(true);
    // display drawing help
    self.showDrawPanel = ko.observable(false);
    // display form
    self.showStandForm = ko.observable(false);
    // display list of stands
    self.showStandList = ko.observable(true);

    self.showNoStandHelp = ko.observable(true);

    self.standList = ko.observableArray();

    self.cancelManageStands = function () {
        self.showStandPanels(false);
        app.properties.viewModel.showPropertyPanels(true);
        app.property_layer.setOpacity(1);
        map.removeLayer(self.stand_layer);
        map.removeLayer(self.property_layer);
        app.drawFeature.featureAdded = app.properties.featureAdded;
        app.selectFeature.activate();
    }

    self.addStandStart = function () {
        app.drawFeature.activate();
        self.showStandHelp(false);
        self.showStandList(false);
        self.showDrawPanel(true);
    }

    self.addStandForm = function () {
        // get the form
        var formUrl = app.workspace["feature-classes"][0]["link-relations"]["create"]["uri-template"];
        // clean up and show the form
        $.get(formUrl, function (data) {
          var $form = $(data).find('form');
          $form.find('input:submit').remove();
          // app.cleanupForm($form);
          $('#stands-form-container').empty().append($form);
          self.showNoStandHelp(false);
          self.showStandForm(true);
          $form.find('input:visible:first').focus();
          $form.bind('submit', function (event) {
            event.preventDefault();
            //self.addFeature(self, event);
          });
        })
        // activate drawing
        // on save submit form and geometry
        // get the new feature and add it to observable array
    }

    self.updateStand = function (stand_id) {
      var updateUrl = '/features/generic-links/links/geojson/{uid}/'.replace('{uid}',stand_id);
      $.get(updateUrl, function (data) {
        self.stand_layer.addFeatures(app.geojson_format.read(data));
      });
    }

    self.associateStand = function (stand_id, property_id) {
        var url = "/features/forestproperty/{forestproperty_uid}/add/{stand_uid}";
        url = url.replace('{forestproperty_uid}', property_id);
        url = url.replace('{stand_uid}', stand_id);
        $.ajax({
            url: url,
            type: "POST",
            success: function (data) {
              self.updateStand(JSON.parse(data)["X-Madrona-Select"]);
              app.stands.viewModel.showStandForm(false);
              app.stands.viewModel.showStandList(true);
              app.new_features.removeAllFeatures();
            }
        })

    }

    self.saveStandForm = function (self, event) {
      var $dialog = $('#stands-form-container'),
        $form = $dialog.find('form'),
        actionUrl = $form.attr('action'),
        values = {}, error=false;
      $form.find('input,select').each(function () {
        var $input = $(this);
        if ($input.closest('.field').hasClass('required') && $input.attr('type') !== 'hidden' && ! $input.val()) {
          error = true;
          $input.closest('.control-group').addClass('error');
          $input.siblings('p.help-block').text('This field is required.');
        } else {
          values[$input.attr('name')] = $input.val();
        }
      });
      if (error) {
        return false;
      }
      values.geometry_final = values.geometry_orig = app.stands.geometry;
      $form.addClass('form-horizontal');
      $.ajax({
        url: actionUrl,
        type: "POST",
        data: values,
        success: function (data, textStatus, jqXHR) {
          // property is not defined if new
          self.associateStand(JSON.parse(data)["X-Madrona-Select"], self.property.uid());
        },
        error: function (jqXHR, textStatus, errorThrown) {
          self.cancelAddStand();
        }
      });
    };

    self.cancelAddStand = function () {
      self.showStandForm(false);
      self.showStandHelp(true);
      self.showStandList(true);
      self.showDrawPanel(false);
      app.new_features.removeAllFeatures()
      app.drawFeature.deactivate();
      self.showNoStandHelp(true);
    }

    self.initialize = function (property) {
      // bind the viewmodel
      ko.applyBindings(self, document.getElementById('stand-html'));

      // start the stand manager
      self.property_layer = new OpenLayers.Layer.Vector("Active Property",  {
                      renderers: app.renderer,
                      styleMap: new OpenLayers.StyleMap({
                              fillOpacity: 0,
                              strokeWidth: 2,
                              strokeOpacity: 1,
                              strokeColor: "#44ff00",
                          })
                  });
      map.addLayer(self.property_layer);
      //map.zoomToExtent(self.property_layer.getDataExtent());
      app.stands.featureAdded = function(feature) {
        app.drawFeature.deactivate();
        self.showDrawPanel(false);
        app.stands.geometry = feature.geometry.toString();
        self.addStandForm();
        //app.saveFeature(feature);
      };

      self.stand_layer = new OpenLayers.Layer.Vector("Stands",  {
                      renderers: app.renderer,
                      styleMap:  new OpenLayers.StyleMap({
                          fillOpacity: .3,
                          fillColor: "white",
                          strokeWidth: 1,
                          strokeOpacity: 1,
                          strokeColor: "orange",
                      })
                  });
      map.addLayer(self.stand_layer);
      self.stand_layer.events.on({
        'featureselected': function (feature) {
        },
        'featureadded': function (feature) {
          var featureViewModel = ko.mapping.fromJS(feature.feature.data);
          // save a reference to the feature
          featureViewModel.feature = feature.feature;
          // add it to the viewmodel
          self.standList.unshift(featureViewModel);
        },
        'featuresadded': function (data) {
        }
      });
      map.addLayer(self.stand_layer);

      self.snapper = new OpenLayers.Control.Snapping({
         layer: app.new_features,
         targets: [self.property_layer, self.stand_layer],
         greedy: false
      });
      self.snapper.activate();
      self.loadStands(property);
    }

    self.reloadStands = function (property) {
      self.stand_layer.removeAllFeatures();
      self.standList.removeAll();
      self.property_layer.removeAllFeatures();
      map.addLayer(self.property_layer);
      map.addLayer(self.stand_layer);
      self.loadStands(property);
      self.showStandList(true);
    }

    self.loadStands = function (property) {
      self.property = property;
      app.drawFeature.featureAdded = app.stands.featureAdded;
      app.selectFeature.deactivate();
      self.property_layer.addFeatures(property.feature.clone());
      $.get('/trees/property_stand_list/{property_id}/'.replace('{property_id}', property.uid()), function (data) {
          if (data.features.length) {
              self.stand_layer.addFeatures(app.geojson_format.read(data));
              self.showStandHelp(false);
          } else {
            self.showStandHelp(true);
          }
      });

    }

    return self;
}

<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var express = require('express')
  , stylus = require('stylus')
  , sio = require('socket.io')
  , path = require('path')
  , fs = require('fs');

/**
 * App.
 */

var app = express.createServer();

/**
 * Initial port to listen to.
 */

var port = 3000;

/**
 * A map of tests to socket.io ports we're listening on.
 */

var testsPorts = {};

/**
 * App configuration.
 */

app.configure(function () {
  app.use(stylus.middleware({ src: __dirname + '/public' }))
  app.use(express.static(__dirname + '/public'));
  app.set('views', __dirname);
  app.set('view engine', 'jade');
});

/**
 * App routes.
 */

app.get('/', function (req, res) {
  res.render('index', {
      layout: false
    , testsPorts: testsPorts
  });
});

/**
 * Sends test files.
 */

app.get('/test/:file', function (req, res) {
  res.sendfile(path.normalize(__dirname + '/../../test/' + req.params.file));
});

/**
 * App listen.
 */

app.listen(port++, function () {
  var addr = app.address();
  console.error('   listening on http://' + addr.address + ':' + addr.port);
});

/**
 * Override handler to simplify development
 */

function handler (req, res) {
  fs.readFile(__dirname + '/../../dist/socket.io.js', 'utf8', function (err, b) {
    if (err) {
      res.writeHead(404);
      res.end('Error');
      return;
    }

    res.writeHead(200, { 'Content-Type': 'application/javascript' });
    res.end(b);
  });
};

/**
 * Socket.IO default server (to serve client)
 */

var io = sio.listen(app);

io.configure(function () {
  io.set('browser client handler', handler);
});

/**
 * Scopes servers for a given test suite.
 */

var currentSuite;

function suite (name, fn) {
  currentSuite = testsPorts[name] = {};
  fn();
};

/**
 * Creates a socket io server
 */

function server (name, fn) {
  currentSuite[name] = port++;
  fn(sio.listen(port));
};

/**
 * Socket.IO servers.
 */

suite('socket.test.js', function () {

  server('test connecting the socket and disconnecting', function (io) {
    io.on('connection', function () {
      console.error('woot');
    });
  });

});
<commit_msg>Fixed style.<commit_after>
/**
 * Module dependencies.
 */

var express = require('express')
  , stylus = require('stylus')
  , sio = require('socket.io')
  , path = require('path')
  , fs = require('fs');

/**
 * App.
 */

var app = express.createServer();

/**
 * Initial port to listen to.
 */

var port = 3000;

/**
 * A map of tests to socket.io ports we're listening on.
 */

var testsPorts = {};

/**
 * App configuration.
 */

app.configure(function () {
  app.use(stylus.middleware({ src: __dirname + '/public' }))
  app.use(express.static(__dirname + '/public'));
  app.set('views', __dirname);
  app.set('view engine', 'jade');
});

/**
 * App routes.
 */

app.get('/', function (req, res) {
  res.render('index', {
      layout: false
    , testsPorts: testsPorts
  });
});

/**
 * Sends test files.
 */

app.get('/test/:file', function (req, res) {
  res.sendfile(path.normalize(__dirname + '/../../test/' + req.params.file));
});

/**
 * App listen.
 */

app.listen(port++, function () {
  var addr = app.address();
  console.error('   listening on http://' + addr.address + ':' + addr.port);
});

/**
 * Override handler to simplify development
 */

function handler (req, res) {
  fs.readFile(__dirname + '/../../dist/socket.io.js', 'utf8', function (err, b) {
    if (err) {
      res.writeHead(404);
      res.end('Error');
      return;
    }

    res.writeHead(200, { 'Content-Type': 'application/javascript' });
    res.end(b);
  });
};

/**
 * Socket.IO default server (to serve client)
 */

var io = sio.listen(app);

io.configure(function () {
  io.set('browser client handler', handler);
});

/**
 * Scopes servers for a given test suite.
 */

var currentSuite;

function suite (name, fn) {
  currentSuite = testsPorts[name] = {};
  fn();
};

/**
 * Creates a socket io server
 */

function server (name, fn) {
  currentSuite[name] = port++;
  fn(sio.listen(port));
};

/**
 * Socket.IO servers.
 */

suite('socket.test.js', function () {

  server('test connecting the socket and disconnecting', function (io) {
    io.on('connection', function () {
      console.error('woot');
    });
  });

});
<|endoftext|>


<commit_before>//Dependencies.
var CoreStore = require('../CoreStore');
var assign = require('object-assign');
var AppDispatcher = require('../../dispatcher');
var keys = require('lodash/object/keys');
var intersection = require('lodash/array/intersection');
/**
 * Default configuration of the search.
 * @type {Object}
 */
/*var defaultSearchConfig = {
  facet:"facet",
  list:"list",
  pageInfos: "pageInfos"
};*/

class SearchStore extends CoreStore {
  constructor(conf){
    var config = assign({}, {definitionPath: "search"}, conf);
    super(config);
  }
  get(){
    return this.data.toJS();
  }
  /**
   * Update all the data from the search.
   * @return {}
  */
  update(newData){
    var previousData = this.data.toJS();
    var processedData = assign(previousData,newData);
    if(previousData.searchContext.scope === newData.searchContext.scope
        && previousData.searchContext.query === newData.searchContext.query){
      processedData.list = previousData.list.concat(newData.list);
    }
    var data = {};
    for(var key in processedData){
      data[key] = Immutable[isArray(dataNode) ? "List" : "Map"](processedData[key]);
    }
    this.data = Immutable.Map(data);
    this.emit('search:change');
  }
  /**
   * Add a listener on the global change on the search store.
   * @param {Function} cb [description]
   */
  addSearchChangeListener(cb){
    this.addListener('search:change', cb);
  }
  /**
   * The store registrer itself on the dispatcher.
   */
  registerDispatcher(){
    var currentStore = this;
    this.dispatch = AppDispatcher.register(function(transferInfo) {
      var defKeys = keys(this.definition); //TODO: a sub part of the keys may be needed.
      var dataKeys = keys(transferInfo.data);
      var intersectKeys = intersection(defKeys, dataKeys);
      if(intersectKeys.length === defKeys.keys){
       this.update(transferInfo.data);
      }
    });
  }
}

module.exports = SearchStore;
<commit_msg>[store][search] fix closure problem<commit_after>//Dependencies.
var CoreStore = require('../CoreStore');
var assign = require('object-assign');
var AppDispatcher = require('../../dispatcher');
var keys = require('lodash/object/keys');
var intersection = require('lodash/array/intersection');
/**
 * Default configuration of the search.
 * @type {Object}
 */
/*var defaultSearchConfig = {
  facet:"facet",
  list:"list",
  pageInfos: "pageInfos"
};*/

class SearchStore extends CoreStore {
  constructor(conf){
    var config = assign({}, {definitionPath: "search"}, conf);
    super(config);
  }
  get(){
    return this.data.toJS();
  }
  /**
   * Update all the data from the search.
   * @return {}
  */
  update(newData){
    var previousData = this.data.toJS();
    var processedData = assign(previousData,newData);
    if(previousData.searchContext.scope === newData.searchContext.scope
        && previousData.searchContext.query === newData.searchContext.query){
      processedData.list = previousData.list.concat(newData.list);
    }
    var data = {};
    for(var key in processedData){
      data[key] = Immutable[isArray(dataNode) ? "List" : "Map"](processedData[key]);
    }
    this.data = Immutable.Map(data);
    this.emit('search:change');
  }
  /**
   * Add a listener on the global change on the search store.
   * @param {Function} cb [description]
   */
  addSearchChangeListener(cb){
    this.addListener('search:change', cb);
  }
  /**
   * The store registrer itself on the dispatcher.
   */
  registerDispatcher(){
    var currentStore = this;
    this.dispatch = AppDispatcher.register(function(transferInfo) {
      var defKeys = keys(currentStore.definition); //TODO: a sub part of the keys may be needed.
      var dataKeys = keys(transferInfo.data);
      var intersectKeys = intersection(defKeys, dataKeys);
      if(intersectKeys.length === defKeys.length){
       currentStore.update(transferInfo.data);
      }
    });
  }
}

module.exports = SearchStore;
<|endoftext|>


<commit_before>$(document).ready(function() {
   init();
});

/* Javascript for stylebot options page */

var bg_window = null;

var cache = {
    modal: null,
    textareaHeight: null
}

// options with their default values
//
var options = {
    useShortcutKey: true,
    contextMenu: true,
    shortcutKey: 77, // keycode for 'm'
    shortcutMetaKey: 'alt',
    mode: "Basic",
    sync: false,
    livePreviewColorPicker: false,
    showPageAction: true
}

var styles = {};

// initialize options
function init() {
    // initialize tabs
    initializeTabs();

    // fetch options from datastore
    fetchOptions();

    $.each(options, function(option, value) {
        var $el = $('[name=' + option + ']');
        var el = $el.get(0);

        if (el == undefined)
            return;

        var tag = el.tagName.toLowerCase();

        if (el.type === "checkbox") {
            if (value == true)
                el.checked = true;
        }

        else if (tag === "select" || el.type === "hidden") {
            if (value != undefined)
                el.value = value;
        }

        else if (el.type === "radio") {
            var len = $el.length;

            for (var i = 0; i < len; i ++) {
                if ($el.get(i).value == value)
                {
                    $el.get(i).checked = true;
                    return true;
                }
            }
        }

    });

    KeyCombo.init($('[name=shortcutKeyCharacter]').get(0), $('[name=shortcutKey]').get(0));

    bg_window = chrome.extension.getBackgroundPage();
    styles = bg_window.cache.styles;

    fillCustomStyles();
    attachListeners();
    initFiltering();
    updateSyncUI();

    // hack to wait for window.innerHeight to be available
    setTimeout(function() {
        cache.textareaHeight = window.innerHeight * 0.5 + 'px';
    }, 100);
}

// Initialize tabs
function initializeTabs() {
    $("ul.menu li:first").addClass("tabActive").show();
    $("#options > div").hide();
    $("#basic-options").show();

    // click event for tab menu items
    $("ul.menu li").click(function() {

        $("ul.menu li").removeClass("tabActive");
        $(this).addClass("tabActive");
        $("#options > div").hide();

        // Get DIV ID for content from the href of the menu link
        var activeTab = $(this).find("a").attr("href");
        $(activeTab).fadeIn();
        return false;
    });
}

// fetches options from the datastore
function fetchOptions() {
    $.each(options, function(option, value) {
        var dataStoreValue = localStorage['stylebot_option_' + option];

        if (dataStoreValue != typeof undefined)
        {
            if (dataStoreValue === "true" || dataStoreValue === "false")
                options[option] = (dataStoreValue === "true");
            else
                options[option] = dataStoreValue;
        }
    });
}

// Attaches listeners for different types of inputs that change option values
function attachListeners() {
    // checkbox
    $('.option label input[type=checkbox]').change(function(e) {
        var name = e.target.name;
        var value = translateOptionValue(name, e.target.checked);
        bg_window.saveOption(name, value);
    });

    // radio
    $('.option label input[type=radio]').change(function(e) {
        var name = e.target.name;
        var value = translateOptionValue(name, e.target.value);
        bg_window.saveOption(name, value);
    });

    // select
    $('.option label select').change(function(e) {
        bg_window.saveOption(e.target.name, e.target.value);
    });

    // textfields
    $('.option label input[type=text]').keyup(function(e) {
        if (e.target.name == "shortcutKeyCharacter")
            option = "shortcutKey";
        else
            option = e.target.name;
        bg_window.saveOption(option, translateOptionValue(option, e.target.value));
    });
}

function translateOptionValue(name, value) {
    switch(name) {
        case "sync": return (value == "true") ? true : false;
        case "shortcutKey": return $('[name=shortcutKey]').attr('value');
    }

    return value;
}

// Custom Styles

// Refreshes the custom styles. Called during initialization and on import
function fillCustomStyles() {
    var container = $("#custom-styles");
    container.html("");

    for (var url in styles) {
        container.append(createCustomStyleOption(url));
    }
}

// Adds a new style to the UI
function createCustomStyleOption(url) {
    var container = $('
', {
        class: 'custom-style'
    });

    var url_div = $('
', {
        html: url,
        class: 'custom-style-url',
        tabIndex: 0
    })
    .data('value', url)
    .appendTo(container);

    Utils.makeEditable(url_div , function(newValue) {
        editURL( url_div.data('value'), newValue);
        url_div.data('value', newValue);
    });

    var b_container = $('
', {
        class: 'button-container'
    });

    $('<button>', {
        html: 'Share',
        class: 'inline-button'
    })
    .click(shareStyle)
    .appendTo(b_container);

    $('<button>', {
        html: 'Edit',
        class: 'inline-button'
    })
    .click(editStyle)
    .appendTo(b_container);

    $('<button>', {
        html: 'Remove',
        class: 'inline-button'
    })
    .click(removeStyle)
    .appendTo(b_container);

    return container.append(b_container);
}

// Called when the remove button is clicked for a style
function removeStyle(e) {
    var parent = $(e.target).parents('.custom-style');
    var url = parent.find('.custom-style-url');
    delete styles[url.html()];
    parent.remove();
    bg_window.saveStyles(styles);
    bg_window.pushStyles();
}

// Displays the modal popup for editing a style
function editStyle(e) {
    var parent = $(e.target).parents('.custom-style');
    var url = parent.find('.custom-style-url').html();
    var rules = styles[url]['_rules'];
    var css = CSSUtils.crunchFormattedCSS(rules, false);

    var html = "
Edit the CSS for " + url + ":
";
    html += "<textarea class='stylebot-css-code' style='width: 100%; height:" + cache.textareaHeight + "'>" + css + "</textarea>";
    html += "<button onclick='cache.modal.hide();'>Cancel</button>";
    html += "<button onclick='onUpdate(); cache.modal.hide();'>Save</button>";

    initModal(html);

    cache.modal.options.onOpen = function() {
        var textarea = cache.modal.box.find('textarea').get(0);
        textarea.focus();
        Utils.moveCursorToEnd(textarea);
    };

    cache.modal.show();
}

// Called when Share button is clicked for a style
function shareStyle(e) {
    var parent = $(e.target).parents('.custom-style');
    var url = parent.find('.custom-style-url').html();
    var rules = styles[url]['_rules'];
    var css = CSSUtils.crunchFormattedCSS(rules, false);

    var production_url = "http://stylebot.me/post";

    // create a form and submit data
    var temp_form = $('<form>', {
        'method': 'post',
        'action': production_url,
        'target': '_blank'
    });

    // site
    $('<input>', {
        type: 'hidden',
        name: 'site',
        value: url
    }).appendTo(temp_form);

    // css
    $('<input>', {
        type: 'hidden',
        name: 'css',
        value: css
    }).appendTo(temp_form);

    $('<submit>').appendTo(temp_form);

    temp_form.submit();

    temp_form.remove();
}

// Called when a style is updated (Update button is clicked)
function onUpdate() {
    var url = cache.modal.box.find('div > b').html();
    var css = cache.modal.box.find('textarea').attr('value');
    saveStyle(url, css);
}

// Displays the modal popup to add a new style
function addStyle() {
    var html = "
URL: <input type='text'></input>
";
    html += "<textarea class='stylebot-css-code' style='width: 100%; height:" + cache.textareaHeight + "'>";
    html += "</textarea>";
    html += "<button onclick= 'cache.modal.hide();' >Cancel</button>";
    html += "<button onclick= 'onAdd(); cache.modal.hide();' >Add</button>";

    initModal(html);
    cache.modal.options.onOpen = function() { cache.modal.box.find('input').focus(); };
    cache.modal.show();
}

// Called when a new style is added (Add button is clicked)
function onAdd() {
    var url = cache.modal.box.find('input').attr('value');
    var css = cache.modal.box.find('textarea').attr('value');
    if (css == "")
        return false;

    if (saveStyle(url, css))
        createCustomStyleOption(url, styles[url]).appendTo($("#custom-styles"));
}

// Saves a style. Called by onUpdate and onAdd
function saveStyle(url, css) {
    var parser = new CSSParser();
    var sheet = parser.parse(css, false, true);
    var retVal = false;

    if (sheet) {
        try {
            var rules = CSSUtils.getRulesFromParserObject(sheet);
            styles[url] = {};
            styles[url]['_rules'] = rules;
            styles[url]['_social'] = {};
            retVal = true;
        }
        catch(e) {}
    }

    // if css is empty. remove the style
    else if (css === "" && styles[url]) {
        delete styles[url];
        $('.custom-style-url:contains(' + url + ')').parent().remove();
    }

    bg_window.saveStyles(styles);
    bg_window.pushStyles();

    return retVal;
}

// Callback for edit in place for URLs
function editURL(oldValue, newValue) {
    if (oldValue == newValue || newValue == "")
        return;
    // going through a loop so that new entry is inserted at the same position
    // otherwise, on changing the url, new entry is inserted at the bottom
    var newStyles = {};
    for (var url in styles) {
        if (url == oldValue) {
            var rules = styles[oldValue];
            newStyles[newValue] = rules;
            delete styles[oldValue];
        }
        else
            newStyles[url] = styles[url];
    }
    styles = newStyles;
    bg_window.saveStyles(styles);
}

// Backup

// Generates JSON string for backup and displays the modal popup containing it
function export() {
    if (styles)
        css = JSON.stringify(styles);
    else
        css = "";

    var html = "
Copy and paste your custom styles into a text file:
<textarea class='stylebot-css-code' style='height:" + (parseInt(cache.textareaHeight,10)+26) + "px'>" + css + "</textarea><button onclick='copyToClipboard()'>Copy To Clipboard</button>";

    initModal(html, {
        closeOnEsc: true,
        closeOnBgClick: true
    });

    cache.modal.options.onOpen = function() {
        var textarea = cache.modal.box.find('textarea').get(0);
        textarea.focus();
        Utils.selectAllText(textarea);
    };

    cache.modal.show();
}

// Copy text in the popup's textarea to clipboard.
function copyToClipboard() {
    chrome.extension.sendRequest({name: "copyToClipboard", text: cache.modal.box.find('textarea').attr('value')}, function(response){});
}

// Displays the modal popup for importing styles from JSON string
function import() {
    var html = "
Paste previously exported custom styles here. \

Note: Current custom styles for similar URLs will be replaced.
 \

 \
    <textarea class='stylebot-css-code' style='height:" + (parseInt(cache.textareaHeight,10)-4) + ";'> \
    </textarea> \
    <button onclick='importCSS();cache.modal.hide();'>Import</button>";

    initModal(html, {
        closeOnEsc: true,
        closeOnBgClick: true
    });

    cache.modal.options.onOpen = function() {
        cache.modal.box.find('textarea').get(0).focus();
    };

    cache.modal.show();
}

// Import styles from JSON string
function importCSS() {
    var json = cache.modal.box.find('textarea').attr('value');

    if (json && json != "")
    {
        $(".custom-style").html("");

        try {
            var imported_styles = JSON.parse(json);
            styles = mergeStyles(imported_styles, styles);
            bg_window.saveStyles(styles);
        }

        catch(e) {
            console.log(e);
        }

        fillCustomStyles();
    }
}

// Sync

// Initialize Sync UI based on value of the sync option
function updateSyncUI() {
    $('#sync-button').html(options.sync ? "Disable Sync" : "Enable Sync");
    $('#sync-enabled-note').toggle(options.sync);
    $('#sync-now').attr('disabled', !options.sync);
}

// Turn syncing on/off
//
function toggleSyncing() {
    options.sync = !options.sync;
    bg_window.saveOption("sync", options.sync);
    if (!options.sync) {
        bg_window.disableSync();
    }
    else {
        bg_window.enableSync(true);
    }
    updateSyncUI();
}

// Toggle display of css icon in omnibar

function togglePageAction() {
    options.showPageAction = !options.showPageAction;
    bg_window.saveOption("showPageAction", options.showPageAction);
    if (!options.showPageAction) {
        bg_window.hidePageActions();
    }
    else {
        bg_window.showPageActions();
    }
}

// Modal popup

// Initialize a new popup
function initModal(html, options) {
    if (!cache.modal)
    {
        cache.modal = new ModalBox(html, {
            bgFadeSpeed: 0,
            closeOnEsc: false,
            closeOnBgClick: false,
            height: window.innerHeight * 0.6
        });
    }

    if (options) {
        cache.modal.options.closeOnEsc = options.closeOnEsc ? true : false;
        cache.modal.options.closeOnBgClick = options.closeOnBgClick ? true : false;
    }

    else {
        cache.modal.options.closeOnEsc = false;
        cache.modal.options.closeOnBgClick = false;
    }

    cache.modal.box.html(html);
}

// Attach listener to search field for filtering styles
function initFiltering() {
    $("#style-search-field").bind('search', function(e){
       filterStyles($(this).val());
    });
}

// Filter styles based on user entered text in search field
function filterStyles(value) {
    var styleDivs = $('.custom-style');
    var urls = $('.custom-style-url');
    var len = styleDivs.length;

    for (var i = 0; i < len; i++) {
        var $div = $(styleDivs[i]);

        if (urls[i].innerHTML.indexOf(value) == -1)
            $div.hide();
        else
            $div.show();
    }
}

// Merges styles from s1 into s2
function mergeStyles(s1, s2) {
    if (!s2) {
        return s1;
    }

    for (var url in s1) {
        // it's the new format
        if (s1[url]['_rules']) {
            s2[url] = s1[url];
        }

        // old format
        else {
            s2[url] = {};
            s2[url]['_rules'] = s1[url];
        }
    }

    return s2;
}<commit_msg>While searching, escape key now has the same behaviour than the x button<commit_after>$(document).ready(function() {
   init();
});

/* Javascript for stylebot options page */

var bg_window = null;

var cache = {
    modal: null,
    textareaHeight: null
}

// options with their default values
//
var options = {
    useShortcutKey: true,
    contextMenu: true,
    shortcutKey: 77, // keycode for 'm'
    shortcutMetaKey: 'alt',
    mode: "Basic",
    sync: false,
    livePreviewColorPicker: false,
    showPageAction: true
}

var styles = {};

// initialize options
function init() {
    // initialize tabs
    initializeTabs();

    // fetch options from datastore
    fetchOptions();

    $.each(options, function(option, value) {
        var $el = $('[name=' + option + ']');
        var el = $el.get(0);

        if (el == undefined)
            return;

        var tag = el.tagName.toLowerCase();

        if (el.type === "checkbox") {
            if (value == true)
                el.checked = true;
        }

        else if (tag === "select" || el.type === "hidden") {
            if (value != undefined)
                el.value = value;
        }

        else if (el.type === "radio") {
            var len = $el.length;

            for (var i = 0; i < len; i ++) {
                if ($el.get(i).value == value)
                {
                    $el.get(i).checked = true;
                    return true;
                }
            }
        }

    });

    KeyCombo.init($('[name=shortcutKeyCharacter]').get(0), $('[name=shortcutKey]').get(0));

    bg_window = chrome.extension.getBackgroundPage();
    styles = bg_window.cache.styles;

    fillCustomStyles();
    attachListeners();
    initFiltering();
    updateSyncUI();

    // hack to wait for window.innerHeight to be available
    setTimeout(function() {
        cache.textareaHeight = window.innerHeight * 0.5 + 'px';
    }, 100);
}

// Initialize tabs
function initializeTabs() {
    $("ul.menu li:first").addClass("tabActive").show();
    $("#options > div").hide();
    $("#basic-options").show();

    // click event for tab menu items
    $("ul.menu li").click(function() {

        $("ul.menu li").removeClass("tabActive");
        $(this).addClass("tabActive");
        $("#options > div").hide();

        // Get DIV ID for content from the href of the menu link
        var activeTab = $(this).find("a").attr("href");
        $(activeTab).fadeIn();
        return false;
    });
}

// fetches options from the datastore
function fetchOptions() {
    $.each(options, function(option, value) {
        var dataStoreValue = localStorage['stylebot_option_' + option];

        if (dataStoreValue != typeof undefined)
        {
            if (dataStoreValue === "true" || dataStoreValue === "false")
                options[option] = (dataStoreValue === "true");
            else
                options[option] = dataStoreValue;
        }
    });
}

// Attaches listeners for different types of inputs that change option values
function attachListeners() {
    // checkbox
    $('.option label input[type=checkbox]').change(function(e) {
        var name = e.target.name;
        var value = translateOptionValue(name, e.target.checked);
        bg_window.saveOption(name, value);
    });

    // radio
    $('.option label input[type=radio]').change(function(e) {
        var name = e.target.name;
        var value = translateOptionValue(name, e.target.value);
        bg_window.saveOption(name, value);
    });

    // select
    $('.option label select').change(function(e) {
        bg_window.saveOption(e.target.name, e.target.value);
    });

    // textfields
    $('.option label input[type=text]').keyup(function(e) {
        if (e.target.name == "shortcutKeyCharacter")
            option = "shortcutKey";
        else
            option = e.target.name;
        bg_window.saveOption(option, translateOptionValue(option, e.target.value));
    });
}

function translateOptionValue(name, value) {
    switch(name) {
        case "sync": return (value == "true") ? true : false;
        case "shortcutKey": return $('[name=shortcutKey]').attr('value');
    }

    return value;
}

// Custom Styles

// Refreshes the custom styles. Called during initialization and on import
function fillCustomStyles() {
    var container = $("#custom-styles");
    container.html("");

    for (var url in styles) {
        container.append(createCustomStyleOption(url));
    }
}

// Adds a new style to the UI
function createCustomStyleOption(url) {
    var container = $('
', {
        class: 'custom-style'
    });

    var url_div = $('
', {
        html: url,
        class: 'custom-style-url',
        tabIndex: 0
    })
    .data('value', url)
    .appendTo(container);

    Utils.makeEditable(url_div , function(newValue) {
        editURL( url_div.data('value'), newValue);
        url_div.data('value', newValue);
    });

    var b_container = $('
', {
        class: 'button-container'
    });

    $('<button>', {
        html: 'Share',
        class: 'inline-button'
    })
    .click(shareStyle)
    .appendTo(b_container);

    $('<button>', {
        html: 'Edit',
        class: 'inline-button'
    })
    .click(editStyle)
    .appendTo(b_container);

    $('<button>', {
        html: 'Remove',
        class: 'inline-button'
    })
    .click(removeStyle)
    .appendTo(b_container);

    return container.append(b_container);
}

// Called when the remove button is clicked for a style
function removeStyle(e) {
    var parent = $(e.target).parents('.custom-style');
    var url = parent.find('.custom-style-url');
    delete styles[url.html()];
    parent.remove();
    bg_window.saveStyles(styles);
    bg_window.pushStyles();
}

// Displays the modal popup for editing a style
function editStyle(e) {
    var parent = $(e.target).parents('.custom-style');
    var url = parent.find('.custom-style-url').html();
    var rules = styles[url]['_rules'];
    var css = CSSUtils.crunchFormattedCSS(rules, false);

    var html = "
Edit the CSS for " + url + ":
";
    html += "<textarea class='stylebot-css-code' style='height:" + (parseInt(cache.textareaHeight,10)+28) + "px'>" + css + "</textarea>";
    html += "<button onclick='cache.modal.hide();'>Cancel</button>";
    html += "<button onclick='onUpdate(); cache.modal.hide();'>Save</button>";

    initModal(html);

    cache.modal.options.onOpen = function() {
        var textarea = cache.modal.box.find('textarea').get(0);
        textarea.focus();
        Utils.moveCursorToEnd(textarea);
    };

    cache.modal.show();
}

// Called when Share button is clicked for a style
function shareStyle(e) {
    var parent = $(e.target).parents('.custom-style');
    var url = parent.find('.custom-style-url').html();
    var rules = styles[url]['_rules'];
    var css = CSSUtils.crunchFormattedCSS(rules, false);

    var production_url = "http://stylebot.me/post";

    // create a form and submit data
    var temp_form = $('<form>', {
        'method': 'post',
        'action': production_url,
        'target': '_blank'
    });

    // site
    $('<input>', {
        type: 'hidden',
        name: 'site',
        value: url
    }).appendTo(temp_form);

    // css
    $('<input>', {
        type: 'hidden',
        name: 'css',
        value: css
    }).appendTo(temp_form);

    $('<submit>').appendTo(temp_form);

    temp_form.submit();

    temp_form.remove();
}

// Called when a style is updated (Update button is clicked)
function onUpdate() {
    var url = cache.modal.box.find('div > b').html();
    var css = cache.modal.box.find('textarea').attr('value');
    saveStyle(url, css);
}

// Displays the modal popup to add a new style
function addStyle() {
    var html = "
URL: <input type='text'></input>
";
    html += "<textarea class='stylebot-css-code' style='width: 100%; height:" + cache.textareaHeight + "'>";
    html += "</textarea>";
    html += "<button onclick= 'cache.modal.hide();' >Cancel</button>";
    html += "<button onclick= 'onAdd(); cache.modal.hide();' >Add</button>";

    initModal(html);
    cache.modal.options.onOpen = function() { cache.modal.box.find('input').focus(); };
    cache.modal.show();
}

// Called when a new style is added (Add button is clicked)
function onAdd() {
    var url = cache.modal.box.find('input').attr('value');
    var css = cache.modal.box.find('textarea').attr('value');
    if (css == "")
        return false;

    if (saveStyle(url, css))
        createCustomStyleOption(url, styles[url]).appendTo($("#custom-styles"));
}

// Saves a style. Called by onUpdate and onAdd
function saveStyle(url, css) {
    var parser = new CSSParser();
    var sheet = parser.parse(css, false, true);
    var retVal = false;

    if (sheet) {
        try {
            var rules = CSSUtils.getRulesFromParserObject(sheet);
            styles[url] = {};
            styles[url]['_rules'] = rules;
            styles[url]['_social'] = {};
            retVal = true;
        }
        catch(e) {}
    }

    // if css is empty. remove the style
    else if (css === "" && styles[url]) {
        delete styles[url];
        $('.custom-style-url:contains(' + url + ')').parent().remove();
    }

    bg_window.saveStyles(styles);
    bg_window.pushStyles();

    return retVal;
}

// Callback for edit in place for URLs
function editURL(oldValue, newValue) {
    if (oldValue == newValue || newValue == "")
        return;
    // going through a loop so that new entry is inserted at the same position
    // otherwise, on changing the url, new entry is inserted at the bottom
    var newStyles = {};
    for (var url in styles) {
        if (url == oldValue) {
            var rules = styles[oldValue];
            newStyles[newValue] = rules;
            delete styles[oldValue];
        }
        else
            newStyles[url] = styles[url];
    }
    styles = newStyles;
    bg_window.saveStyles(styles);
}

// Backup

// Generates JSON string for backup and displays the modal popup containing it
function export() {
    if (styles)
        css = JSON.stringify(styles);
    else
        css = "";

    var html = "
Copy and paste your custom styles into a text file:
<textarea class='stylebot-css-code' style='height:" + (parseInt(cache.textareaHeight,10)+26) + "px'>" + css + "</textarea><button onclick='copyToClipboard()'>Copy To Clipboard</button>";

    initModal(html, {
        closeOnEsc: true,
        closeOnBgClick: true
    });

    cache.modal.options.onOpen = function() {
        var textarea = cache.modal.box.find('textarea').get(0);
        textarea.focus();
        Utils.selectAllText(textarea);
    };

    cache.modal.show();
}

// Copy text in the popup's textarea to clipboard.
function copyToClipboard() {
    chrome.extension.sendRequest({name: "copyToClipboard", text: cache.modal.box.find('textarea').attr('value')}, function(response){});
}

// Displays the modal popup for importing styles from JSON string
function import() {
    var html = "
Paste previously exported custom styles here. \

Note: Current custom styles for similar URLs will be replaced.
 \

 \
    <textarea class='stylebot-css-code' style='height:" + (parseInt(cache.textareaHeight,10)-8) + ";'> \
    </textarea> \
    <button onclick='importCSS();cache.modal.hide();'>Import</button>";

    initModal(html, {
        closeOnEsc: true,
        closeOnBgClick: true
    });

    cache.modal.options.onOpen = function() {
        cache.modal.box.find('textarea').get(0).focus();
    };

    cache.modal.show();
}

// Import styles from JSON string
function importCSS() {
    var json = cache.modal.box.find('textarea').attr('value');

    if (json && json != "")
    {
        $(".custom-style").html("");

        try {
            var imported_styles = JSON.parse(json);
            styles = mergeStyles(imported_styles, styles);
            bg_window.saveStyles(styles);
        }

        catch(e) {
            console.log(e);
        }

        fillCustomStyles();
    }
}

// Sync

// Initialize Sync UI based on value of the sync option
function updateSyncUI() {
    $('#sync-button').html(options.sync ? "Disable Sync" : "Enable Sync");
    $('#sync-enabled-note').toggle(options.sync);
    $('#sync-now').attr('disabled', !options.sync);
}

// Turn syncing on/off
//
function toggleSyncing() {
    options.sync = !options.sync;
    bg_window.saveOption("sync", options.sync);
    if (!options.sync) {
        bg_window.disableSync();
    }
    else {
        bg_window.enableSync(true);
    }
    updateSyncUI();
}

// Toggle display of css icon in omnibar

function togglePageAction() {
    options.showPageAction = !options.showPageAction;
    bg_window.saveOption("showPageAction", options.showPageAction);
    if (!options.showPageAction) {
        bg_window.hidePageActions();
    }
    else {
        bg_window.showPageActions();
    }
}

// Modal popup

// Initialize a new popup
function initModal(html, options) {
    if (!cache.modal)
    {
        cache.modal = new ModalBox(html, {
            bgFadeSpeed: 0,
            closeOnEsc: false,
            closeOnBgClick: false,
            height: window.innerHeight * 0.6
        });
    }

    if (options) {
        cache.modal.options.closeOnEsc = options.closeOnEsc ? true : false;
        cache.modal.options.closeOnBgClick = options.closeOnBgClick ? true : false;
    }

    else {
        cache.modal.options.closeOnEsc = false;
        cache.modal.options.closeOnBgClick = false;
    }

    cache.modal.box.html(html);
}

// Attach listener to search field for filtering styles
function initFiltering() {
    $("#style-search-field").bind('search', function(e){
       filterStyles($(this).val());
    }).keyup(function(e){
        if(e.keyCode == 27){
            $(this).val('')
            filterStyles("");
        }
    });
}

// Filter styles based on user entered text in search field
function filterStyles(value) {
    var styleDivs = $('.custom-style');
    var urls = $('.custom-style-url');
    var len = styleDivs.length;

    for (var i = 0; i < len; i++) {
        var $div = $(styleDivs[i]);

        if (urls[i].innerHTML.indexOf(value) == -1)
            $div.hide();
        else
            $div.show();
    }
}

// Merges styles from s1 into s2
function mergeStyles(s1, s2) {
    if (!s2) {
        return s1;
    }

    for (var url in s1) {
        // it's the new format
        if (s1[url]['_rules']) {
            s2[url] = s1[url];
        }

        // old format
        else {
            s2[url] = {};
            s2[url]['_rules'] = s1[url];
        }
    }

    return s2;
}<|endoftext|>


<commit_before>'use strict';

var config = require('./config/config'),
    promise = require('bluebird'),
    debug = require('debug')('photobooth'),
    fs = require('fs'),
    gcloud = require('gcloud')({
      projectId: config.google.project,
      keyFilename: config.google.keyFilename,
    }),
    gm = require('gm'),
    moment = require('moment'),
    request = require('request-promise'),
    tmp = require('tmp');

function joinImages(photos) {
  var deferred = promise.pending();
  tmp.tmpName(function(err, path) {
    var destPath = path + '.jpg';
    debug('Writing montage to ' + destPath);

    // Join them together in a vertical strip with 10px padding and resize to
    // max width of 1024.  I had to use the manually constructed command because
    // otherwise .geometry() is added after input arguments and doesn't actually
    // set the maximum tile size.
    gm(photos[3]).command('montage')
      .in('-geometry')
      .in('+10+10')
      .in('-tile')
      .in('2x2')
      .in(photos[0])
      .in(photos[1])
      .in(photos[2])
      .write(destPath, function() {
        deferred.resolve(destPath);
      });
  });
  return deferred.promise;
}

function uploadToStorage(path) {
  debug('Uploading to cloud storage.');
  var deferred = promise.pending();
  var gcs = gcloud.storage();
  var bucket = gcs.bucket(config.google.storageBucket);

  var filename = 'photobooth-' +
      moment().format('YYYYMMDD-hhmmss-SSS' + '.jpg');
  var options = {
    destination: filename
  };
  bucket.upload(path, options, function(err, file) {
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve({bucket: file.metadata.bucket,
                        name: file.metadata.name});
    }
  });
  return deferred.promise;
}

function recordInFrontend(storageInfo) {
  debug('Recording in frontend.');
  if (config.eventId) {
    storageInfo.event = config.eventId;
  }
  return request.post(config.frontend.host + '/photos').form(storageInfo)
    .then(JSON.parse);
}

function printReceipt(photo) {
  debug('Printing receipt.');
  var url = config.frontend.host + '/' + photo._id;
  console.info('Photo available at: ' + url);

  // TODO: Print receipt
  return promise.resolve(url);
}

var Photobooth = function(panel, camera, ui, server) {
  var pendingInterval,
      pendingTimeout,
      pendingPromise,
      previewHandle,
      photos = [],
      currentState = '',
      finishedPhoto;

  function handleError(err) {
    console.trace(err);
    error();
  }

  function resetPhotos() {
    if (photos.length) {
      photos.forEach(function(path) {
        fs.unlink(path);
      });
      photos = [];
    }

    if (finishedPhoto) {
      fs.unlink(finishedPhoto);
      finishedPhoto = null;
    }
  }

  function transition(newState) {
    debug('Transitioning: ' + currentState + ' -> ' + newState);
    if (pendingTimeout) {
      clearTimeout(pendingTimeout);
    }
    if (pendingInterval) {
      clearInterval(pendingInterval);
    }
    if (pendingPromise) {
      pendingPromise.cancel();
    }
    panel.removeAllListeners('activate');
    currentState = newState;
  }

  function cleanup() {
    resetPhotos();
    if (previewHandle) {
      previewHandle.close();
    }
  }

  // State -> idle
  function idle() {
    transition('idle');
    cleanup();
    ui.idle();
    panel.on('activate', preview);
  }

  // State -> preview
  function preview() {
    transition('preview');
    resetPhotos();
    panel.on('activate', pending);

    previewHandle = camera.openPreview();
    server.setSourceStream(previewHandle.stream);
    ui.preview();

    // Timeout back to idle after 2 minutes.
    pendingTimeout = setTimeout(idle, 1000*60*2);  // 2 minutes.
  }

  // State -> pending
  function pending() {
    transition('pending');
    var secondsRemaining = config.countdown;
    previewHandle = camera.openPreview();
    server.setSourceStream(previewHandle.stream);

    pendingPromise = previewHandle.ready;
    previewHandle.ready.then(function() {
      // Initialize UI and then count down to zero.
      ui.pending(secondsRemaining, photos);
      function tick() {
        --secondsRemaining;
        ui.pending(secondsRemaining, photos);
        if (secondsRemaining > 1) {
          pendingTimeout = setTimeout(tick, 1000);
        } else {
          capture();
        }
      }
      pendingTimeout = setTimeout(tick, 1000);
    }, error);
  }

  // State -> capture
  function capture() {
    transition('capture');
    pendingPromise = camera.reset()
      .tap(function() {
        ui.capture(photos);
      })
      .then(camera.takePhoto.bind(camera))
      .then(function(photo) {
        photos.push(photo);
        if (photos.length == 4) {
          // Make sure the new photo is shown.
          ui.capture(photos);
          processing();
        } else {
          // Make sure the new photo is shown.
          ui.capture(photos);
          pending();
        }
      }, handleError);
  }

  // State -> processing
  function processing() {
    transition('processing');
    ui.processing(photos, "Composing images");
    pendingPromise = joinImages(photos)
      .then(function(path) {
        finishedPhoto = path;
        return promise.resolve(path);
      })
      .tap(finished)
      .then(uploadToStorage)
      .then(recordInFrontend)
      .then(printReceipt)
      .then(null, handleError);
  }

  // State -> error
  function error() {
    transition('error');
    cleanup();
    ui.error();
    pendingTimeout = setTimeout(idle, 1000*60*1);
  }

  // State -> finished
  function finished(url) {
    transition('finished');
    ui.finished(finishedPhoto, url);
    pendingTimeout = setTimeout(idle, 1000*60*1);
    panel.on('activate', preview);
  }

  this.run = function() {
    transition('init');
    panel.on('reset', idle);
    setImmediate(idle);
  };
};
module.exports = Photobooth;
<commit_msg>Add test image to idle call.<commit_after>'use strict';

var config = require('./config/config'),
    promise = require('bluebird'),
    debug = require('debug')('photobooth'),
    fs = require('fs'),
    gcloud = require('gcloud')({
      projectId: config.google.project,
      keyFilename: config.google.keyFilename,
    }),
    gm = require('gm'),
    moment = require('moment'),
    request = require('request-promise'),
    tmp = require('tmp');

function joinImages(photos) {
  var deferred = promise.pending();
  tmp.tmpName(function(err, path) {
    var destPath = path + '.jpg';
    debug('Writing montage to ' + destPath);

    // Join them together in a vertical strip with 10px padding and resize to
    // max width of 1024.  I had to use the manually constructed command because
    // otherwise .geometry() is added after input arguments and doesn't actually
    // set the maximum tile size.
    gm(photos[3]).command('montage')
      .in('-geometry')
      .in('+10+10')
      .in('-tile')
      .in('2x2')
      .in(photos[0])
      .in(photos[1])
      .in(photos[2])
      .write(destPath, function() {
        deferred.resolve(destPath);
      });
  });
  return deferred.promise;
}

function uploadToStorage(path) {
  debug('Uploading to cloud storage.');
  var deferred = promise.pending();
  var gcs = gcloud.storage();
  var bucket = gcs.bucket(config.google.storageBucket);

  var filename = 'photobooth-' +
      moment().format('YYYYMMDD-hhmmss-SSS' + '.jpg');
  var options = {
    destination: filename
  };
  bucket.upload(path, options, function(err, file) {
    if (err) {
      deferred.reject(err);
    } else {
      deferred.resolve({bucket: file.metadata.bucket,
                        name: file.metadata.name});
    }
  });
  return deferred.promise;
}

function recordInFrontend(storageInfo) {
  debug('Recording in frontend.');
  if (config.eventId) {
    storageInfo.event = config.eventId;
  }
  return request.post(config.frontend.host + '/photos').form(storageInfo)
    .then(JSON.parse);
}

function printReceipt(photo) {
  debug('Printing receipt.');
  var url = config.frontend.host + '/' + photo._id;
  console.info('Photo available at: ' + url);

  // TODO: Print receipt
  return promise.resolve(url);
}

var Photobooth = function(panel, camera, ui, server) {
  var pendingInterval,
      pendingTimeout,
      pendingPromise,
      previewHandle,
      photos = [],
      currentState = '',
      finishedPhoto;

  function handleError(err) {
    console.trace(err);
    error();
  }

  function resetPhotos() {
    if (photos.length) {
      photos.forEach(function(path) {
        fs.unlink(path);
      });
      photos = [];
    }

    if (finishedPhoto) {
      fs.unlink(finishedPhoto);
      finishedPhoto = null;
    }
  }

  function transition(newState) {
    debug('Transitioning: ' + currentState + ' -> ' + newState);
    if (pendingTimeout) {
      clearTimeout(pendingTimeout);
    }
    if (pendingInterval) {
      clearInterval(pendingInterval);
    }
    if (pendingPromise) {
      pendingPromise.cancel();
    }
    panel.removeAllListeners('activate');
    currentState = newState;
  }

  function cleanup() {
    resetPhotos();
    if (previewHandle) {
      previewHandle.close();
    }
  }

  // State -> idle
  function idle() {
    transition('idle');
    cleanup();
    ui.idle("http://storage.googleapis.com/bmt-photobooth-staging/photobooth-20151017-021430-996.jpg");
    panel.on('activate', preview);
  }

  // State -> preview
  function preview() {
    transition('preview');
    resetPhotos();
    panel.on('activate', pending);

    previewHandle = camera.openPreview();
    server.setSourceStream(previewHandle.stream);
    ui.preview();

    // Timeout back to idle after 2 minutes.
    pendingTimeout = setTimeout(idle, 1000*60*2);  // 2 minutes.
  }

  // State -> pending
  function pending() {
    transition('pending');
    var secondsRemaining = config.countdown;
    previewHandle = camera.openPreview();
    server.setSourceStream(previewHandle.stream);

    pendingPromise = previewHandle.ready;
    previewHandle.ready.then(function() {
      // Initialize UI and then count down to zero.
      ui.pending(secondsRemaining, photos);
      function tick() {
        --secondsRemaining;
        ui.pending(secondsRemaining, photos);
        if (secondsRemaining > 1) {
          pendingTimeout = setTimeout(tick, 1000);
        } else {
          capture();
        }
      }
      pendingTimeout = setTimeout(tick, 1000);
    }, error);
  }

  // State -> capture
  function capture() {
    transition('capture');
    pendingPromise = camera.reset()
      .tap(function() {
        ui.capture(photos);
      })
      .then(camera.takePhoto.bind(camera))
      .then(function(photo) {
        photos.push(photo);
        if (photos.length == 4) {
          // Make sure the new photo is shown.
          ui.capture(photos);
          processing();
        } else {
          // Make sure the new photo is shown.
          ui.capture(photos);
          pending();
        }
      }, handleError);
  }

  // State -> processing
  function processing() {
    transition('processing');
    ui.processing(photos, "Composing images");
    pendingPromise = joinImages(photos)
      .then(function(path) {
        finishedPhoto = path;
        return promise.resolve(path);
      })
      .tap(finished)
      .then(uploadToStorage)
      .then(recordInFrontend)
      .then(printReceipt)
      .then(null, handleError);
  }

  // State -> error
  function error() {
    transition('error');
    cleanup();
    ui.error();
    pendingTimeout = setTimeout(idle, 1000*60*1);
  }

  // State -> finished
  function finished(url) {
    transition('finished');
    ui.finished(finishedPhoto, url);
    pendingTimeout = setTimeout(idle, 1000*60*1);
    panel.on('activate', preview);
  }

  this.run = function() {
    transition('init');
    panel.on('reset', idle);
    setImmediate(idle);
  };
};
module.exports = Photobooth;
<|endoftext|>


<commit_before>/* @flow */
import isEqual from 'lodash.isequal';

import type {
  MessageState,
  MessageAction,
  MessageFetchCompleteAction,
  EventReactionAddAction,
  EventReactionRemoveAction,
  EventNewMessageAction,
  EventMessageDeleteAction,
  EventUpdateMessageAction,
} from '../types';
import {
  APP_REFRESH,
  LOGOUT,
  LOGIN_SUCCESS,
  ACCOUNT_SWITCH,
  MESSAGE_FETCH_COMPLETE,
  EVENT_NEW_MESSAGE,
  EVENT_MESSAGE_DELETE,
  EVENT_REACTION_ADD,
  EVENT_REACTION_REMOVE,
  EVENT_UPDATE_MESSAGE,
} from '../actionConstants';
import { isMessageInNarrow, getNarrowFromMessage } from '../utils/narrow';
import { groupItemsById } from '../utils/misc';
import chatUpdater from './chatUpdater';
import { NULL_ARRAY, NULL_OBJECT } from '../nullObjects';

const initialState: MessageState = NULL_OBJECT;

const messageFetchComplete = (
  state: MessageState,
  action: MessageFetchCompleteAction,
): MessageState => {
  if (action.messages.length === 0) {
    return state;
  }

  const key = JSON.stringify(action.narrow);
  const messages = state[key] || NULL_ARRAY;
  const messagesById = groupItemsById(messages);
  const replaceExisting = action.anchor === 0 || action.anchor === Number.MAX_SAFE_INTEGER;
  const newMessages = replaceExisting
    ? action.messages.map(
        item =>
          messagesById[item.id]
            ? isEqual(messagesById[item.id], item) ? messagesById[item.id] : item
            : item,
      )
    : action.messages
        .filter(x => !messagesById[x.id])
        .concat(messages)
        .sort((a, b) => a.id - b.id);

  return {
    ...state,
    [key]: newMessages,
  };
};

const eventReactionAdd = (state: MessageState, action: EventReactionAddAction): MessageState =>
  chatUpdater(state, action.messageId, oldMessage => ({
    ...oldMessage,
    reactions: oldMessage.reactions.concat({
      emoji_name: action.emoji,
      user: action.user,
    }),
  }));

const eventReactionRemove = (
  state: MessageState,
  action: EventReactionRemoveAction,
): MessageState =>
  chatUpdater(state, action.messageId, oldMessage => ({
    ...oldMessage,
    reactions: oldMessage.reactions.filter(
      x => !(x.emoji_name === action.emoji && x.user.email === action.user.email),
    ),
  }));

const eventNewMessage = (state: MessageState, action: EventNewMessageAction): MessageState => {
  let stateChange = false;
  let newState = Object.keys(state).reduce((msg, key) => {
    const isInNarrow = isMessageInNarrow(action.message, JSON.parse(key), action.ownEmail);

    if (
      isInNarrow &&
      (action.caughtUp[key] && action.caughtUp[key].newer) &&
      state[key].find(item => action.message.id === item.id) === undefined
    ) {
      stateChange = true;
      msg[key] = [...state[key], action.message];
    } else {
      msg[key] = state[key];
    }

    return msg;
  }, {});

  const key = JSON.stringify(getNarrowFromMessage(action.message, action.ownEmail));
  if (!stateChange && state[key] === undefined) {
    // new message is in new narrow in which we don't have any message
    stateChange = true;
    newState = {
      ...state,
      [key]: [action.message],
    };
  }

  return stateChange ? newState : state;
};

const eventMessageDelete = (
  state: MessageState,
  action: EventMessageDeleteAction,
): MessageState => {
  let stateChange = false;

  const newState = Object.keys(state).reduce((updatedState, key) => {
    updatedState[key] = state[key].filter(msg => msg.id !== action.messageId);
    stateChange = stateChange || updatedState[key].length < state[key].length;
    return updatedState;
  }, {});

  return stateChange ? newState : state;
};

const eventUpdateMessage = (state: MessageState, action: EventUpdateMessageAction): MessageState =>
  chatUpdater(state, action.message_id, oldMessage => ({
    ...oldMessage,
    content: action.rendered_content || oldMessage.content,
    subject: action.subject || oldMessage.subject,
    subject_links: action.subject_links || oldMessage.subject_links,
    edit_history: [
      action.orig_rendered_content
        ? action.orig_subject
          ? {
              prev_rendered_content: action.orig_rendered_content,
              prev_subject: oldMessage.subject,
              timestamp: action.edit_timestamp,
              prev_rendered_content_version: action.prev_rendered_content_version,
              user_id: action.user_id,
            }
          : {
              prev_rendered_content: action.orig_rendered_content,
              timestamp: action.edit_timestamp,
              prev_rendered_content_version: action.prev_rendered_content_version,
              user_id: action.user_id,
            }
        : {
            prev_subject: oldMessage.subject,
            timestamp: action.edit_timestamp,
            user_id: action.user_id,
          },
      ...(oldMessage.edit_history || NULL_ARRAY),
    ],
    last_edit_timestamp: action.edit_timestamp,
  }));

export default (state: MessageState = initialState, action: MessageAction): MessageState => {
  switch (action.type) {
    case APP_REFRESH:
    case LOGOUT:
    case LOGIN_SUCCESS:
    case ACCOUNT_SWITCH:
      return initialState;

    case MESSAGE_FETCH_COMPLETE:
      return messageFetchComplete(state, action);

    case EVENT_REACTION_ADD:
      return eventReactionAdd(state, action);

    case EVENT_REACTION_REMOVE:
      return eventReactionRemove(state, action);

    case EVENT_NEW_MESSAGE:
      return eventNewMessage(state, action);

    case EVENT_MESSAGE_DELETE:
      return eventMessageDelete(state, action);

    case EVENT_UPDATE_MESSAGE:
      return eventUpdateMessage(state, action);

    default:
      return state;
  }
};
<commit_msg>refactor: Make login in eventNewMessage more clear<commit_after>/* @flow */
import isEqual from 'lodash.isequal';

import type {
  MessageState,
  MessageAction,
  MessageFetchCompleteAction,
  EventReactionAddAction,
  EventReactionRemoveAction,
  EventNewMessageAction,
  EventMessageDeleteAction,
  EventUpdateMessageAction,
} from '../types';
import {
  APP_REFRESH,
  LOGOUT,
  LOGIN_SUCCESS,
  ACCOUNT_SWITCH,
  MESSAGE_FETCH_COMPLETE,
  EVENT_NEW_MESSAGE,
  EVENT_MESSAGE_DELETE,
  EVENT_REACTION_ADD,
  EVENT_REACTION_REMOVE,
  EVENT_UPDATE_MESSAGE,
} from '../actionConstants';
import { isMessageInNarrow, getNarrowFromMessage } from '../utils/narrow';
import { groupItemsById } from '../utils/misc';
import chatUpdater from './chatUpdater';
import { NULL_ARRAY, NULL_OBJECT } from '../nullObjects';

const initialState: MessageState = NULL_OBJECT;

const messageFetchComplete = (
  state: MessageState,
  action: MessageFetchCompleteAction,
): MessageState => {
  if (action.messages.length === 0) {
    return state;
  }

  const key = JSON.stringify(action.narrow);
  const messages = state[key] || NULL_ARRAY;
  const messagesById = groupItemsById(messages);
  const replaceExisting = action.anchor === 0 || action.anchor === Number.MAX_SAFE_INTEGER;
  const newMessages = replaceExisting
    ? action.messages.map(
        item =>
          messagesById[item.id]
            ? isEqual(messagesById[item.id], item) ? messagesById[item.id] : item
            : item,
      )
    : action.messages
        .filter(x => !messagesById[x.id])
        .concat(messages)
        .sort((a, b) => a.id - b.id);

  return {
    ...state,
    [key]: newMessages,
  };
};

const eventReactionAdd = (state: MessageState, action: EventReactionAddAction): MessageState =>
  chatUpdater(state, action.messageId, oldMessage => ({
    ...oldMessage,
    reactions: oldMessage.reactions.concat({
      emoji_name: action.emoji,
      user: action.user,
    }),
  }));

const eventReactionRemove = (
  state: MessageState,
  action: EventReactionRemoveAction,
): MessageState =>
  chatUpdater(state, action.messageId, oldMessage => ({
    ...oldMessage,
    reactions: oldMessage.reactions.filter(
      x => !(x.emoji_name === action.emoji && x.user.email === action.user.email),
    ),
  }));

const eventNewMessage = (state: MessageState, action: EventNewMessageAction): MessageState => {
  let stateChange = false;
  let newState = Object.keys(state).reduce((msg, key) => {
    const isInNarrow = isMessageInNarrow(action.message, JSON.parse(key), action.ownEmail);
    const isCaughtUp = action.caughtUp[key] && action.caughtUp[key].newer;
    const messageDoesNotExist =
      state[key].find(item => action.message.id === item.id) === undefined;

    if (isInNarrow && isCaughtUp && messageDoesNotExist) {
      stateChange = true;
      msg[key] = [...state[key], action.message];
    } else {
      msg[key] = state[key];
    }

    return msg;
  }, {});

  const key = JSON.stringify(getNarrowFromMessage(action.message, action.ownEmail));
  if (!stateChange && state[key] === undefined) {
    // new message is in new narrow in which we don't have any message
    stateChange = true;
    newState = {
      ...state,
      [key]: [action.message],
    };
  }

  return stateChange ? newState : state;
};

const eventMessageDelete = (
  state: MessageState,
  action: EventMessageDeleteAction,
): MessageState => {
  let stateChange = false;

  const newState = Object.keys(state).reduce((updatedState, key) => {
    updatedState[key] = state[key].filter(msg => msg.id !== action.messageId);
    stateChange = stateChange || updatedState[key].length < state[key].length;
    return updatedState;
  }, {});

  return stateChange ? newState : state;
};

const eventUpdateMessage = (state: MessageState, action: EventUpdateMessageAction): MessageState =>
  chatUpdater(state, action.message_id, oldMessage => ({
    ...oldMessage,
    content: action.rendered_content || oldMessage.content,
    subject: action.subject || oldMessage.subject,
    subject_links: action.subject_links || oldMessage.subject_links,
    edit_history: [
      action.orig_rendered_content
        ? action.orig_subject
          ? {
              prev_rendered_content: action.orig_rendered_content,
              prev_subject: oldMessage.subject,
              timestamp: action.edit_timestamp,
              prev_rendered_content_version: action.prev_rendered_content_version,
              user_id: action.user_id,
            }
          : {
              prev_rendered_content: action.orig_rendered_content,
              timestamp: action.edit_timestamp,
              prev_rendered_content_version: action.prev_rendered_content_version,
              user_id: action.user_id,
            }
        : {
            prev_subject: oldMessage.subject,
            timestamp: action.edit_timestamp,
            user_id: action.user_id,
          },
      ...(oldMessage.edit_history || NULL_ARRAY),
    ],
    last_edit_timestamp: action.edit_timestamp,
  }));

export default (state: MessageState = initialState, action: MessageAction): MessageState => {
  switch (action.type) {
    case APP_REFRESH:
    case LOGOUT:
    case LOGIN_SUCCESS:
    case ACCOUNT_SWITCH:
      return initialState;

    case MESSAGE_FETCH_COMPLETE:
      return messageFetchComplete(state, action);

    case EVENT_REACTION_ADD:
      return eventReactionAdd(state, action);

    case EVENT_REACTION_REMOVE:
      return eventReactionRemove(state, action);

    case EVENT_NEW_MESSAGE:
      return eventNewMessage(state, action);

    case EVENT_MESSAGE_DELETE:
      return eventMessageDelete(state, action);

    case EVENT_UPDATE_MESSAGE:
      return eventUpdateMessage(state, action);

    default:
      return state;
  }
};
<|endoftext|>