<commit_before>/**
 * This module handles all clientside searching
 */

const config = require('collections-online/shared/config');
const helpers = require('../../../shared/helpers');

const MapController = require('map-controller');
require('./search-freetext-form');

const getSearchParams = require('./get-parameters');
const elasticsearchQueryBody = require('./es-query-body');
const elasticsearchAggregationsBody = require('./es-aggregations-body');
const generateQuerystring = require('./generate-querystring');
const resultsHeader = require('./results-header');
const sorting = require('./sorting');
const navigator = require('../document/navigator');

const templates = {
  searchResultItem: require('views/includes/search-results-item')
};

// How many assets should be loaded at once?
const PAGE_SIZE = 24;
module.exports.PAGE_SIZE = PAGE_SIZE;

let resultsDesired = PAGE_SIZE;
let resultsLoaded = [];
let resultsTotal = Number.MAX_SAFE_INTEGER;
let loadingResults = false;

// Controls whether we should fetch search-results for a map or a list.
let viewMode = 'list';

let elasticsearch = require('elasticsearch');
let es = new elasticsearch.Client({
  host: location.origin + '/api'
});

function initialize() {
  const $searchInput = $('.search-freetext-form__input');
  const $results = $('#results');
  // Button shown at the end of the list search result that triggers the load
  // of more results.
  const $loadMoreBtn = $('#load-more-btn');
  const $noResultsText = $('#no-results-text');

  function reset() {
    resultsLoaded = [];
    resultsTotal = Number.MAX_SAFE_INTEGER;
    resultsDesired = PAGE_SIZE;
    $(window).off('scroll');
    $loadMoreBtn.addClass('invisible');
  }

  /**
   * Perform a search and update the map or list.
   *
   * @param updateWidgets
   *   Whether this update should trigger refresh of the related widgets.
   *
   * @param indicateLoading
   *   Whether to show a overlay as the results are loading.
   *
   * @param searchParams
   *   Optional override search parameters.
   */
  function update(updateWidgets, indicateLoading, searchParams) {
    // If the indicateLoading is not set - default to true
    if(typeof(indicateLoading) === 'undefined') {
      indicateLoading = true;
    }

    // Get search parameters if they have not been provided.
    if (!searchParams) {
      searchParams = getSearchParams();
    }

    // Update the filter-bar.
    sorting.update(searchParams);

    // Update the freetext search input, q contains the querystring.
    $searchInput.val(searchParams.filters.q);

    // Update the page title
    const title = helpers.generateSearchTitle(searchParams.filters);
    $('head title').text(title + ' - ' + config.siteTitle);
    loadingResults = true;

    // A fresh update is the first of potentially many updates with the same
    // search parameters.
    updateWidgets = resultsLoaded.length === 0 || updateWidgets;

    if(updateWidgets) {
      if(config.features.filterSidebar) {
        const sidebar = require('./filter-sidebar');
        // Update the sidebar right away
        sidebar.update(searchParams.filters, null);
        // Get aggragations for the sidebar
        es.search({
          body: elasticsearchAggregationsBody(searchParams),
          size: 0
        }).then(function (response) {
          sidebar.update(searchParams.filters, response.aggregations);
        }, function (error) {
          console.trace(error.message);
        });
      }
    }

    let resultCallback = function (resultsTotal) {
      // Update the results header with the result
      if(updateWidgets) {
        resultsHeader.update(searchParams, resultsTotal);
        if(indicateLoading) {
          $('.search-results').removeClass('search-results--loading');
        }
      }
    };

    // Do search depending on current viewmode.
    if (viewMode === 'map') {
      mapController.onUpdate(searchParams, function(updatedParams){
        // Generate the query body, this will inject a bounds filter if we added
        // one above.
        let queryBody = elasticsearchQueryBody(updatedParams);

        // Let plugins modify the search body.
        if(typeof(helpers.modifySearchQueryBody) === 'function') {
          // If a modifySearchQueryBody helper is defined, call it
          queryBody = helpers.modifySearchQueryBody(queryBody, updatedParams);
        }

        if (updatedParams.geohash) {
          queryBody.aggregations = {
            'geohash_grid' : {
              'geohash_grid' : {
                'field' : 'location',
                'precision' : config.search.geohashPrecision
              }
            }
          };
        }

        const maxAssets = config.search.maxAssetMarkers;
        // We've configured our search, now setup the query and execute it.
        const searchObject = {
          body: queryBody,
          // We only want the aggregation so we don't care about the hits.
          size: updatedParams.geohash ? 0 : maxAssets,
          _source: [
            'location',
            'longitude',
            'latitude',
            'collection',
            'id',
            'short_title',
            'type',
            'heading',
            'description'
          ],
        };

        // Make sure we persist, but replace state so that we don't end up in
        // the back/forward history.
        persistChangedParams(updatedParams, true);

        es.search(searchObject).then(function (response) {
          resultsTotal = response.hits.total;
          loadingResults = false;
          mapController.onResults(response, updatedParams);
        }, function (error) {
          console.trace(error.message);
        });
      });
    } else if (viewMode === 'list') {
      if (updateWidgets) {
        // Start updating the results header
        resultsHeader.update(searchParams, resultsTotal);
        // Update the results header before the result comes in
        if(indicateLoading) {
          $('.search-results').addClass('search-results--loading');
        }
      }
      resultsTotal = updateList(searchParams, updateWidgets, resultCallback);
    }
  }

  /**
   * Update the search result list.
   */
  function updateList(searchParams, updateWidgets, resultCallback) {
    // Generate the query body
    let queryBody = elasticsearchQueryBody(searchParams);

    if(typeof(helpers.modifySearchQueryBody) === 'function') {
      // If a modifySearchQueryBody helper is defined, call it
      queryBody = helpers.modifySearchQueryBody(queryBody, searchParams);
    }

    const searchObject = {
      body: queryBody,
      from: resultsLoaded.length,
      _source: ['collection', 'id', 'short_title', 'type', 'description'],
      size: resultsDesired - resultsLoaded.length
    };

    // Pull in the search results.
    es.search(searchObject).then(function (response) {
      // If no results are loaded yet, it might be because we just called reset
      if(resultsLoaded.length === 0) {
        // Remove all search result items from $results, that might be there
        $results.find('.search-results-item').remove();
      }
      resultsTotal = response.hits.total;
      loadingResults = false;

      response.hits.hits.forEach(function(hit) {
        const item = {
          type: hit._type,
          metadata: hit._source
        };
        const markup = templates.searchResultItem(item);
        $results.append(markup);
        resultsLoaded.push(item);
      });

      // Save the results loaded in the session storage, so we can use them on
      // out other places.
      navigator.save({
        resultsLoaded, queryBody
      });

      // Replace the state of in the history if supported
      if(history.replaceState) {
        history.replaceState({
          resultsLoaded,
          resultsTotal
        }, null, null);
      }

      // Show some text if we don't have any results
      if (resultsTotal === 0) {
        $noResultsText.removeClass('hidden');
      } else {
        $noResultsText.addClass('hidden');
      }

      // If we have not loaded all available results, let's show the btn to load
      if(updateWidgets && resultsLoaded.length < resultsTotal) {
        $loadMoreBtn.removeClass('invisible');
      } else {
        $loadMoreBtn.addClass('invisible');
      }

      resultCallback(resultsTotal);
    }, function (error) {
      console.trace(error.message);
    });
  }

  /**
   * Store changed params into the browser history.
   *
   * @param searchParams
   *   The search params to persist.
   * @param replaceState
   *   Whether to replace state or push a new (the latter lets the browser move
   *   back and forth between states).
   */
  function persistChangedParams (searchParams, replaceState = false) {
    // Change the URL
    if (history) {
      reset();
      // Update the browser history and the url with our new parameters.

      var state = {searchParams: searchParams};
      var url = location.pathname + generateQuerystring(searchParams);
      if (replaceState) {
        history.replaceState(state, '', url);
      }
      else {
        history.pushState(state, '', url);
      }
    }
    else {
      throw new Error('History API is required');
    }
  }

  function enableEndlessScrolling() {
    $loadMoreBtn.addClass('invisible');
    $(window).on('scroll', function(e) {
      var $lastResult = $('#results .search-results-item:last-child');
      if($lastResult.length > 0) {
        var lastResultOffset = $lastResult.offset();
        var scrollTop = $(window).scrollTop();
        var scrollBottom = scrollTop + $(window).height();
        if(scrollBottom > lastResultOffset.top && !loadingResults) {
          resultsDesired += PAGE_SIZE;
          update();
        }
      }
    }).scroll();
  }

  /**
   * Check if the user has any relevant history data during init and use it.
   *
   * @param state
   *   The history.state.
   */
  function inflateHistoryState(state) {
    // Render results from the state
    if(state.resultsLoaded) {
      reset();
      // Remove all the search result items right away
      $results.find('.search-results-item').remove();

      // Append rendered markup, once per asset loaded from the state.
      resultsLoaded = state.resultsLoaded;
      resultsDesired = resultsLoaded.length;
      resultsLoaded.forEach(function(item) {
        var markup = templates.searchResultItem(item);
        $results.append(markup);
      });

      // Replace the resultsTotal from the state
      resultsTotal = state.resultsTotal;

      // Using the updateWidgets=true, updates the header as well
      // Using the indicateLoading=false makes sure the UI doesn't blink
      update(true, false);
    }
  }

  // When the user navigates the state, update it
  window.addEventListener('popstate', function(event) {
    inflateHistoryState(event.state);
  }, false);

  const searchControllerCallbacks = {
    // Allow the caller to refresh the current search-results.
    refresh: function(searchParameters) {
      // Allow the caller to provide initial search parameteres. If none are
      // given use our own defaults.
      if (!searchParameters) {
        searchParameters = getSearchParams();
      }
      update(true, false, searchParameters);
    },

    getCurrentSearchParameters: function (){
      return getSearchParams();
    },
  };

  // Get any initial parameters from the url and pass the relevant part to the
  // map.
  var options = {};
  var searchParams = getSearchParams();
  if (searchParams.map) {
    options.mapInitParam = searchParams.map;
  }
  // Setup the map with a callback it can use when it needs a new search
  // triggered and a configuration for when we switch between hash and asset
  // search-results.
  const mapController = MapController(document.getElementById('map'), searchControllerCallbacks, options);

  // *** Register event handlers ***
  $('#sidebar, #sidebarmobile, #filters, #filtersmobile').on('click', '.btn', function() {
    var action = $(this).data('action');
    var field = $(this).data('field');
    var filter = config.search.filters[field];
    var value = $(this).data('value');
    if(!value && filter.type === 'date-interval-range') {
      var $form = $(this).closest('.form-group');
      var from = $form.find('[name='+field+'-from]').val() || '*';
      var to = $form.find('[name='+field+'-to]').val() || '*';
      if(from !== '*' || to !== '*') {
        value = from.replace(/-/g, '/') + '-' + to.replace(/-/g, '/');
      } else {
        return;
      }
    }
    var searchParams = getSearchParams();
    var filters = searchParams.filters;
    if(action === 'add-filter') {
      if(typeof(filters[field]) === 'object') {
        filters[field].push(value);
      } else {
        filters[field] = [value];
      }
      // Store changed parameters and trigger a search.
      persistChangedParams(searchParams);
      update();
    } else if(action === 'remove-filter') {
      if(typeof(filters[field]) === 'object') {
        filters[field] = filters[field].filter(function(v) {
          return v !== value;
        });
      } else {
        delete filters[field];
      }
      // Store changed parameters and trigger a search.
      persistChangedParams(searchParams);
      update();
    }
  });

  $('#sorting-menu').on('click', '.dropdown__options a', function() {
    var sorting = $(this).data('value');
    var searchParams = getSearchParams();
    searchParams.sorting = sorting;
    // Store changed parameters and trigger a search.
    persistChangedParams(searchParams);
    update();
  });

  // Enabled the load-more button
  $loadMoreBtn.on('click', function() {
    enableEndlessScrolling();
  });

  // Toggle filtersection visibility on mobile
  $('#sidebar, #sidebarmobile').on('click', '[data-action="show-filters"]', function() {
    var filterSection = $(this).data('id') + '-filters';
    var $filterSection = $('[data-id="' + filterSection + '"]');
    var wasExpanded = $(this).hasClass('expanded');
    var visibleClass = 'search-filter-sidebar__filters--expanded';

    if (!wasExpanded) {
      $(this).addClass('expanded');
      $filterSection.addClass(visibleClass);
    } else {
      $(this).removeClass('expanded');
      $filterSection.removeClass(visibleClass);
    }
  });

  // Toggle filterbar menus
  $('.filterbar--mobile__button--filters').on('click', function() {
    $('body').addClass('has-filter-open');
  });

  $('.filterbar--mobile__button--close').on('click', function() {
    $('body').removeClass('has-filter-open');
  });

  // When the view-mode is changed by the view-mode selector (view-mode.js),
  // store the new value and trigger a search update.
  $('body').on('search:viewModeChanged', function(e, eventViewMode) {
    viewMode = eventViewMode;
    $('body').trigger('search:update');
  });

  // Let anyone with access to the body element trigger search-updates.
  $('body').on('search:update', function() {
    update(true, true);
  });

  $('.search-filter-sidebar__tab').on('click', '[data-action="show-filterbar-menu"]', function() {
    var wasExpanded = $(this).hasClass('expanded');
    var $parentItem = $(this).closest('.filterbar__item');
    var $filterbar = $(this).closest('.filterbar');

    $filterbar.find('.filterbar__menu').hide();
    $filterbar.find('.expanded').each(function() {
      $(this).removeClass('expanded');
    });

    if (!wasExpanded) {
      $(this).addClass('expanded');
      $parentItem.find('.filterbar__tab').addClass('expanded');
      $parentItem.find('.filterbar__menu').show();
    } else {
      $(this).removeClass('expanded');
      $parentItem.find('.filterbar__tab').removeClass('expanded');
      $parentItem.find('.filterbar__menu').hide();
    }
  });

  // Take control over the search form.
  $searchInput.closest('form').submit(function(e) {
    e.preventDefault();
    var queryString = $searchInput.val() || '';
    var searchParams = getSearchParams();
    searchParams.filters.q = queryString;
    // Store changed parameters and trigger a search.
    persistChangedParams(searchParams);
    update();
  });

  const currentParams = getSearchParams();
  if (currentParams.map) {
    // If the url contains a map parameter, set us in map mode.
    $('body').trigger('search:viewModeChanged', ['map']);
  } else {
    // Default view-mode, so no need to switch, just go ahead and do a search.
    update();
  }
}

// If the path is right - let's initialize
if(decodeURIComponent(location.pathname) === '/' + config.search.path) {
  $(initialize);
}
<commit_msg>Pick up initial center from configuration.<commit_after>/**
 * This module handles all clientside searching
 */

const config = require('collections-online/shared/config');
const helpers = require('../../../shared/helpers');

const MapController = require('map-controller');
require('./search-freetext-form');

const getSearchParams = require('./get-parameters');
const elasticsearchQueryBody = require('./es-query-body');
const elasticsearchAggregationsBody = require('./es-aggregations-body');
const generateQuerystring = require('./generate-querystring');
const resultsHeader = require('./results-header');
const sorting = require('./sorting');
const navigator = require('../document/navigator');

const templates = {
  searchResultItem: require('views/includes/search-results-item')
};

// How many assets should be loaded at once?
const PAGE_SIZE = 24;
module.exports.PAGE_SIZE = PAGE_SIZE;

let resultsDesired = PAGE_SIZE;
let resultsLoaded = [];
let resultsTotal = Number.MAX_SAFE_INTEGER;
let loadingResults = false;

// Controls whether we should fetch search-results for a map or a list.
let viewMode = 'list';

let elasticsearch = require('elasticsearch');
let es = new elasticsearch.Client({
  host: location.origin + '/api'
});

function initialize() {
  const $searchInput = $('.search-freetext-form__input');
  const $results = $('#results');
  // Button shown at the end of the list search result that triggers the load
  // of more results.
  const $loadMoreBtn = $('#load-more-btn');
  const $noResultsText = $('#no-results-text');

  function reset() {
    resultsLoaded = [];
    resultsTotal = Number.MAX_SAFE_INTEGER;
    resultsDesired = PAGE_SIZE;
    $(window).off('scroll');
    $loadMoreBtn.addClass('invisible');
  }

  /**
   * Perform a search and update the map or list.
   *
   * @param updateWidgets
   *   Whether this update should trigger refresh of the related widgets.
   *
   * @param indicateLoading
   *   Whether to show a overlay as the results are loading.
   *
   * @param searchParams
   *   Optional override search parameters.
   */
  function update(updateWidgets, indicateLoading, searchParams) {
    // If the indicateLoading is not set - default to true
    if(typeof(indicateLoading) === 'undefined') {
      indicateLoading = true;
    }

    // Get search parameters if they have not been provided.
    if (!searchParams) {
      searchParams = getSearchParams();
    }

    // Update the filter-bar.
    sorting.update(searchParams);

    // Update the freetext search input, q contains the querystring.
    $searchInput.val(searchParams.filters.q);

    // Update the page title
    const title = helpers.generateSearchTitle(searchParams.filters);
    $('head title').text(title + ' - ' + config.siteTitle);
    loadingResults = true;

    // A fresh update is the first of potentially many updates with the same
    // search parameters.
    updateWidgets = resultsLoaded.length === 0 || updateWidgets;

    if(updateWidgets) {
      if(config.features.filterSidebar) {
        const sidebar = require('./filter-sidebar');
        // Update the sidebar right away
        sidebar.update(searchParams.filters, null);
        // Get aggragations for the sidebar
        es.search({
          body: elasticsearchAggregationsBody(searchParams),
          size: 0
        }).then(function (response) {
          sidebar.update(searchParams.filters, response.aggregations);
        }, function (error) {
          console.trace(error.message);
        });
      }
    }

    let resultCallback = function (resultsTotal) {
      // Update the results header with the result
      if(updateWidgets) {
        resultsHeader.update(searchParams, resultsTotal);
        if(indicateLoading) {
          $('.search-results').removeClass('search-results--loading');
        }
      }
    };

    // Do search depending on current viewmode.
    if (viewMode === 'map') {
      mapController.onUpdate(searchParams, function(updatedParams){
        // Generate the query body, this will inject a bounds filter if we added
        // one above.
        let queryBody = elasticsearchQueryBody(updatedParams);

        // Let plugins modify the search body.
        if(typeof(helpers.modifySearchQueryBody) === 'function') {
          // If a modifySearchQueryBody helper is defined, call it
          queryBody = helpers.modifySearchQueryBody(queryBody, updatedParams);
        }

        if (updatedParams.geohash) {
          queryBody.aggregations = {
            'geohash_grid' : {
              'geohash_grid' : {
                'field' : 'location',
                'precision' : config.search.geohashPrecision
              }
            }
          };
        }

        const maxAssets = config.search.maxAssetMarkers;
        // We've configured our search, now setup the query and execute it.
        const searchObject = {
          body: queryBody,
          // We only want the aggregation so we don't care about the hits.
          size: updatedParams.geohash ? 0 : maxAssets,
          _source: [
            'location',
            'longitude',
            'latitude',
            'collection',
            'id',
            'short_title',
            'type',
            'heading',
            'description'
          ],
        };

        // Make sure we persist, but replace state so that we don't end up in
        // the back/forward history.
        persistChangedParams(updatedParams, true);

        es.search(searchObject).then(function (response) {
          resultsTotal = response.hits.total;
          loadingResults = false;
          mapController.onResults(response, updatedParams);
        }, function (error) {
          console.trace(error.message);
        });
      });
    } else if (viewMode === 'list') {
      if (updateWidgets) {
        // Start updating the results header
        resultsHeader.update(searchParams, resultsTotal);
        // Update the results header before the result comes in
        if(indicateLoading) {
          $('.search-results').addClass('search-results--loading');
        }
      }
      resultsTotal = updateList(searchParams, updateWidgets, resultCallback);
    }
  }

  /**
   * Update the search result list.
   */
  function updateList(searchParams, updateWidgets, resultCallback) {
    // Generate the query body
    let queryBody = elasticsearchQueryBody(searchParams);

    if(typeof(helpers.modifySearchQueryBody) === 'function') {
      // If a modifySearchQueryBody helper is defined, call it
      queryBody = helpers.modifySearchQueryBody(queryBody, searchParams);
    }

    const searchObject = {
      body: queryBody,
      from: resultsLoaded.length,
      _source: ['collection', 'id', 'short_title', 'type', 'description'],
      size: resultsDesired - resultsLoaded.length
    };

    // Pull in the search results.
    es.search(searchObject).then(function (response) {
      // If no results are loaded yet, it might be because we just called reset
      if(resultsLoaded.length === 0) {
        // Remove all search result items from $results, that might be there
        $results.find('.search-results-item').remove();
      }
      resultsTotal = response.hits.total;
      loadingResults = false;

      response.hits.hits.forEach(function(hit) {
        const item = {
          type: hit._type,
          metadata: hit._source
        };
        const markup = templates.searchResultItem(item);
        $results.append(markup);
        resultsLoaded.push(item);
      });

      // Save the results loaded in the session storage, so we can use them on
      // out other places.
      navigator.save({
        resultsLoaded, queryBody
      });

      // Replace the state of in the history if supported
      if(history.replaceState) {
        history.replaceState({
          resultsLoaded,
          resultsTotal
        }, null, null);
      }

      // Show some text if we don't have any results
      if (resultsTotal === 0) {
        $noResultsText.removeClass('hidden');
      } else {
        $noResultsText.addClass('hidden');
      }

      // If we have not loaded all available results, let's show the btn to load
      if(updateWidgets && resultsLoaded.length < resultsTotal) {
        $loadMoreBtn.removeClass('invisible');
      } else {
        $loadMoreBtn.addClass('invisible');
      }

      resultCallback(resultsTotal);
    }, function (error) {
      console.trace(error.message);
    });
  }

  /**
   * Store changed params into the browser history.
   *
   * @param searchParams
   *   The search params to persist.
   * @param replaceState
   *   Whether to replace state or push a new (the latter lets the browser move
   *   back and forth between states).
   */
  function persistChangedParams (searchParams, replaceState = false) {
    // Change the URL
    if (history) {
      reset();
      // Update the browser history and the url with our new parameters.

      var state = {searchParams: searchParams};
      var url = location.pathname + generateQuerystring(searchParams);
      if (replaceState) {
        history.replaceState(state, '', url);
      }
      else {
        history.pushState(state, '', url);
      }
    }
    else {
      throw new Error('History API is required');
    }
  }

  function enableEndlessScrolling() {
    $loadMoreBtn.addClass('invisible');
    $(window).on('scroll', function(e) {
      var $lastResult = $('#results .search-results-item:last-child');
      if($lastResult.length > 0) {
        var lastResultOffset = $lastResult.offset();
        var scrollTop = $(window).scrollTop();
        var scrollBottom = scrollTop + $(window).height();
        if(scrollBottom > lastResultOffset.top && !loadingResults) {
          resultsDesired += PAGE_SIZE;
          update();
        }
      }
    }).scroll();
  }

  /**
   * Check if the user has any relevant history data during init and use it.
   *
   * @param state
   *   The history.state.
   */
  function inflateHistoryState(state) {
    // Render results from the state
    if(state.resultsLoaded) {
      reset();
      // Remove all the search result items right away
      $results.find('.search-results-item').remove();

      // Append rendered markup, once per asset loaded from the state.
      resultsLoaded = state.resultsLoaded;
      resultsDesired = resultsLoaded.length;
      resultsLoaded.forEach(function(item) {
        var markup = templates.searchResultItem(item);
        $results.append(markup);
      });

      // Replace the resultsTotal from the state
      resultsTotal = state.resultsTotal;

      // Using the updateWidgets=true, updates the header as well
      // Using the indicateLoading=false makes sure the UI doesn't blink
      update(true, false);
    }
  }

  // When the user navigates the state, update it
  window.addEventListener('popstate', function(event) {
    inflateHistoryState(event.state);
  }, false);

  const searchControllerCallbacks = {
    // Allow the caller to refresh the current search-results.
    refresh: function(searchParameters) {
      // Allow the caller to provide initial search parameteres. If none are
      // given use our own defaults.
      if (!searchParameters) {
        searchParameters = getSearchParams();
      }
      update(true, false, searchParameters);
    },

    getCurrentSearchParameters: function (){
      return getSearchParams();
    },
  };

  // Get any initial parameters from the url and pass the relevant part to the
  // map.
  var options = {};

  // Set initial center if specified via config.
  if (config.geoTagging && config.geoTagging.initialCenter && config.geoTagging.initialCenter['lon'] && config.geoTagging.initialCenter['lat']) {
    options.initialCenter = [
      config.geoTagging.initialCenter['lon'],
      config.geoTagging.initialCenter['lat']
    ];
  }

  var searchParams = getSearchParams();
  if (searchParams.map) {
    options.mapInitParam = searchParams.map;
  }
  // Setup the map with a callback it can use when it needs a new search
  // triggered and a configuration for when we switch between hash and asset
  // search-results.
  const mapController = MapController(document.getElementById('map'), searchControllerCallbacks, options);

  // *** Register event handlers ***
  $('#sidebar, #sidebarmobile, #filters, #filtersmobile').on('click', '.btn', function() {
    var action = $(this).data('action');
    var field = $(this).data('field');
    var filter = config.search.filters[field];
    var value = $(this).data('value');
    if(!value && filter.type === 'date-interval-range') {
      var $form = $(this).closest('.form-group');
      var from = $form.find('[name='+field+'-from]').val() || '*';
      var to = $form.find('[name='+field+'-to]').val() || '*';
      if(from !== '*' || to !== '*') {
        value = from.replace(/-/g, '/') + '-' + to.replace(/-/g, '/');
      } else {
        return;
      }
    }
    var searchParams = getSearchParams();
    var filters = searchParams.filters;
    if(action === 'add-filter') {
      if(typeof(filters[field]) === 'object') {
        filters[field].push(value);
      } else {
        filters[field] = [value];
      }
      // Store changed parameters and trigger a search.
      persistChangedParams(searchParams);
      update();
    } else if(action === 'remove-filter') {
      if(typeof(filters[field]) === 'object') {
        filters[field] = filters[field].filter(function(v) {
          return v !== value;
        });
      } else {
        delete filters[field];
      }
      // Store changed parameters and trigger a search.
      persistChangedParams(searchParams);
      update();
    }
  });

  $('#sorting-menu').on('click', '.dropdown__options a', function() {
    var sorting = $(this).data('value');
    var searchParams = getSearchParams();
    searchParams.sorting = sorting;
    // Store changed parameters and trigger a search.
    persistChangedParams(searchParams);
    update();
  });

  // Enabled the load-more button
  $loadMoreBtn.on('click', function() {
    enableEndlessScrolling();
  });

  // Toggle filtersection visibility on mobile
  $('#sidebar, #sidebarmobile').on('click', '[data-action="show-filters"]', function() {
    var filterSection = $(this).data('id') + '-filters';
    var $filterSection = $('[data-id="' + filterSection + '"]');
    var wasExpanded = $(this).hasClass('expanded');
    var visibleClass = 'search-filter-sidebar__filters--expanded';

    if (!wasExpanded) {
      $(this).addClass('expanded');
      $filterSection.addClass(visibleClass);
    } else {
      $(this).removeClass('expanded');
      $filterSection.removeClass(visibleClass);
    }
  });

  // Toggle filterbar menus
  $('.filterbar--mobile__button--filters').on('click', function() {
    $('body').addClass('has-filter-open');
  });

  $('.filterbar--mobile__button--close').on('click', function() {
    $('body').removeClass('has-filter-open');
  });

  // When the view-mode is changed by the view-mode selector (view-mode.js),
  // store the new value and trigger a search update.
  $('body').on('search:viewModeChanged', function(e, eventViewMode) {
    viewMode = eventViewMode;
    $('body').trigger('search:update');
  });

  // Let anyone with access to the body element trigger search-updates.
  $('body').on('search:update', function() {
    update(true, true);
  });

  $('.search-filter-sidebar__tab').on('click', '[data-action="show-filterbar-menu"]', function() {
    var wasExpanded = $(this).hasClass('expanded');
    var $parentItem = $(this).closest('.filterbar__item');
    var $filterbar = $(this).closest('.filterbar');

    $filterbar.find('.filterbar__menu').hide();
    $filterbar.find('.expanded').each(function() {
      $(this).removeClass('expanded');
    });

    if (!wasExpanded) {
      $(this).addClass('expanded');
      $parentItem.find('.filterbar__tab').addClass('expanded');
      $parentItem.find('.filterbar__menu').show();
    } else {
      $(this).removeClass('expanded');
      $parentItem.find('.filterbar__tab').removeClass('expanded');
      $parentItem.find('.filterbar__menu').hide();
    }
  });

  // Take control over the search form.
  $searchInput.closest('form').submit(function(e) {
    e.preventDefault();
    var queryString = $searchInput.val() || '';
    var searchParams = getSearchParams();
    searchParams.filters.q = queryString;
    // Store changed parameters and trigger a search.
    persistChangedParams(searchParams);
    update();
  });

  const currentParams = getSearchParams();
  if (currentParams.map) {
    // If the url contains a map parameter, set us in map mode.
    $('body').trigger('search:viewModeChanged', ['map']);
  } else {
    // Default view-mode, so no need to switch, just go ahead and do a search.
    update();
  }
}

// If the path is right - let's initialize
if(decodeURIComponent(location.pathname) === '/' + config.search.path) {
  $(initialize);
}
<|endoftext|>


<commit_before>'use babel';
/*jshint esversion: 6 */

export default class PanelCommentView {

    constructor(serializedState) {
        // Create root element
        this.element = document.createElement('div');
        this.element.classList.add('panel-comment');
        const message = document.createElement('div');
        message.textContent = "panel comment : ";
        message.classList.add('message');
        this.element.appendChild(message);
        const commentary = document.createElement('div');
        commentary.classList.add('commentaire');
        commentary.id = "commentary";
        this.element.appendChild(commentary);
    }

    /**
     * function add text
     * @return void
     * Add a div in the commentary element
     */

    add_text(text) {
        // console.log(text_arr);
        var commentary = document.getElementById("commentary");
        var code = document.createElement('div');
        code.classList.add('commentary');
        code.textContent = text;
        commentary.appendChild(code);
    }

    addBlock(text) {
        var commentary = document.getElementById("commentary");
        var block = document.createElement('div');
        block.classList.add('blockCommentary');
        if (text.includes("* function")) block.classList.add('function_name');
        if (text.includes("* @return")) block.classList.add('return_type');
        block.classList.add('blockCommentaryDescribe');
        block.textContent = text;
        commentary.appendChild(block);
    }

    filter_text() {
        var text_to_parse = this.element.querySelectorAll(".commentary");
        var regex_parsing = /\*(.*)/g;
        var text_arr = [];
        for (var i = 0; i < text_to_parse.length; i++) {
            text = text_to_parse[i].innerHTML; // Get the inner Text
            text_arr.push(text.match(regex_parsing));
        }
        return text_arr;
    }

    /**
     * function clean
     * @return void
     * get all element with class 'code' and destroy them
     */

    clean() {
        var commentary = document.getElementById("commentary");
        while (commentary.firstChild) {
    }

    // Returns an object that can be retrieved when package is activated
    serialize() {}

    // Tear down any state and detach
    destroy() {
        this.element.remove();
    }

    getElement() {
        return this.element;
    }
}
<commit_msg>Oups, bad removal<commit_after>'use babel';
/*jshint esversion: 6 */

export default class PanelCommentView {

    constructor(serializedState) {
        // Create root element
        this.element = document.createElement('div');
        this.element.classList.add('panel-comment');
        const message = document.createElement('div');
        message.textContent = "panel comment : ";
        message.classList.add('message');
        this.element.appendChild(message);
        const commentary = document.createElement('div');
        commentary.classList.add('commentaire');
        commentary.id = "commentary";
        this.element.appendChild(commentary);
    }

    /**
     * function add text
     * @return void
     * Add a div in the commentary element
     */

    add_text(text) {
        // console.log(text_arr);
        var commentary = document.getElementById("commentary");
        var code = document.createElement('div');
        code.classList.add('commentary');
        code.textContent = text;
        commentary.appendChild(code);
    }

    addBlock(text) {
        var commentary = document.getElementById("commentary");
        var block = document.createElement('div');
        block.classList.add('blockCommentary');
        if (text.includes("* function")) block.classList.add('function_name');
        if (text.includes("* @return")) block.classList.add('return_type');
        block.classList.add('blockCommentaryDescribe');
        block.textContent = text;
        commentary.appendChild(block);
    }

    filter_text() {
        var text_to_parse = this.element.querySelectorAll(".commentary");
        var regex_parsing = /\*(.*)/g;
        var text_arr = [];
        for (var i = 0; i < text_to_parse.length; i++) {
            text = text_to_parse[i].innerHTML; // Get the inner Text
            text_arr.push(text.match(regex_parsing));
        }
        return text_arr;
    }

    /**
     * function clean
     * @return void
     * get all element with class 'code' and destroy them
     */

    clean() {
        var commentary = document.getElementById("commentary");
        while (commentary.firstChild) {
            commentary.removeChild(commentary.firstChild);
        }
    }

    // Returns an object that can be retrieved when package is activated
    serialize() {}

    // Tear down any state and detach
    destroy() {
        this.element.remove();
    }

    getElement() {
        return this.element;
    }
}
<|endoftext|>


<commit_before>'use strict';

var is = require( '../../is' );

var elesfn = ({

  closenessCentralityNormalized: function( options ){
    options = options || {};

    var cy = this.cy();

    var harmonic = options.harmonic;
    if( harmonic === undefined ){
      harmonic = true;
    }

    var closenesses = {};
    var maxCloseness = 0;
    var nodes = this.nodes();
    var fw = this.floydWarshall( { weight: options.weight, directed: options.directed } );

    // Compute closeness for every node and find the maximum closeness
    for( var i = 0; i < nodes.length; i++ ){
      var currCloseness = 0;
      for( var j = 0; j < nodes.length; j++ ){
        if( i != j ){
          var d = fw.distance( nodes[ i ], nodes[ j ] );

          if( harmonic ){
            currCloseness += 1 / d;
          } else {
            currCloseness += d;
          }
        }
      }

      if( !harmonic ){
        currCloseness = 1 / currCloseness;
      }

      if( maxCloseness < currCloseness ){
        maxCloseness = currCloseness;
      }

      closenesses[ nodes[ i ].id() ] = currCloseness;
    }

    return {
      closeness: function( node ){
		if( maxCloseness == 0 )
			return 0;

        if( is.string( node ) ){
          // from is a selector string
          var node = (cy.filter( node )[0]).id();
        } else {
          // from is a node
          var node = node.id();
        }

        return closenesses[ node ] / maxCloseness;
      }
    };
  },

  // Implemented from pseudocode from wikipedia
  closenessCentrality: function( options ){
    options = options || {};

    // root - mandatory!
    if( options.root != null ){
      if( is.string( options.root ) ){
        // use it as a selector, e.g. "#rootID
        var root = this.filter( options.root )[0];
      } else {
        var root = options.root[0];
      }
    } else {
      return undefined;
    }

    // weight - optional
    if( options.weight != null && is.fn( options.weight ) ){
      var weight = options.weight;
    } else {
      var weight = function(){return 1;};
    }

    // directed - optional
    if( options.directed != null && is.bool( options.directed ) ){
      var directed = options.directed;
    } else {
      var directed = false;
    }

    var harmonic = options.harmonic;
    if( harmonic === undefined ){
      harmonic = true;
    }

    // we need distance from this node to every other node
    var dijkstra = this.dijkstra( {
      root: root,
      weight: weight,
      directed: directed
    } );
    var totalDistance = 0;

    var nodes = this.nodes();
    for( var i = 0; i < nodes.length; i++ ){
      if( nodes[ i ].id() != root.id() ){
        var d = dijkstra.distanceTo( nodes[ i ] );

        if( harmonic ){
          totalDistance += 1 / d;
        } else {
          totalDistance += d;
        }
      }
    }

    return harmonic ? totalDistance : 1 / totalDistance;
  } // closenessCentrality

}); // elesfn

// nice, short mathemathical alias
elesfn.cc = elesfn.closenessCentrality;
elesfn.ccn = elesfn.closenessCentralityNormalised = elesfn.closenessCentralityNormalized;

module.exports = elesfn;
<commit_msg>lint : no tabs<commit_after>'use strict';

var is = require( '../../is' );

var elesfn = ({

  closenessCentralityNormalized: function( options ){
    options = options || {};

    var cy = this.cy();

    var harmonic = options.harmonic;
    if( harmonic === undefined ){
      harmonic = true;
    }

    var closenesses = {};
    var maxCloseness = 0;
    var nodes = this.nodes();
    var fw = this.floydWarshall( { weight: options.weight, directed: options.directed } );

    // Compute closeness for every node and find the maximum closeness
    for( var i = 0; i < nodes.length; i++ ){
      var currCloseness = 0;
      for( var j = 0; j < nodes.length; j++ ){
        if( i != j ){
          var d = fw.distance( nodes[ i ], nodes[ j ] );

          if( harmonic ){
            currCloseness += 1 / d;
          } else {
            currCloseness += d;
          }
        }
      }

      if( !harmonic ){
        currCloseness = 1 / currCloseness;
      }

      if( maxCloseness < currCloseness ){
        maxCloseness = currCloseness;
      }

      closenesses[ nodes[ i ].id() ] = currCloseness;
    }

    return {
      closeness: function( node ){
        if( maxCloseness == 0 ){ return 0; }

        if( is.string( node ) ){
          // from is a selector string
          var node = (cy.filter( node )[0]).id();
        } else {
          // from is a node
          var node = node.id();
        }

        return closenesses[ node ] / maxCloseness;
      }
    };
  },

  // Implemented from pseudocode from wikipedia
  closenessCentrality: function( options ){
    options = options || {};

    // root - mandatory!
    if( options.root != null ){
      if( is.string( options.root ) ){
        // use it as a selector, e.g. "#rootID
        var root = this.filter( options.root )[0];
      } else {
        var root = options.root[0];
      }
    } else {
      return undefined;
    }

    // weight - optional
    if( options.weight != null && is.fn( options.weight ) ){
      var weight = options.weight;
    } else {
      var weight = function(){return 1;};
    }

    // directed - optional
    if( options.directed != null && is.bool( options.directed ) ){
      var directed = options.directed;
    } else {
      var directed = false;
    }

    var harmonic = options.harmonic;
    if( harmonic === undefined ){
      harmonic = true;
    }

    // we need distance from this node to every other node
    var dijkstra = this.dijkstra( {
      root: root,
      weight: weight,
      directed: directed
    } );
    var totalDistance = 0;

    var nodes = this.nodes();
    for( var i = 0; i < nodes.length; i++ ){
      if( nodes[ i ].id() != root.id() ){
        var d = dijkstra.distanceTo( nodes[ i ] );

        if( harmonic ){
          totalDistance += 1 / d;
        } else {
          totalDistance += d;
        }
      }
    }

    return harmonic ? totalDistance : 1 / totalDistance;
  } // closenessCentrality

}); // elesfn

// nice, short mathemathical alias
elesfn.cc = elesfn.closenessCentrality;
elesfn.ccn = elesfn.closenessCentralityNormalised = elesfn.closenessCentralityNormalized;

module.exports = elesfn;
<|endoftext|>


<commit_before><commit_msg>Update project fingerprintjs2 to 1.0.0-rc1<commit_after><|endoftext|>


<commit_before>var striptags = require('striptags'),
  dom = require('../services/dom'),
  getInput = require('../services/get-input');

/**
 * toggle classes on elements
 * @param {boolean} isTooLong
 * @param {Element} span
 * @param {Element} input
 */
function toggleClass(isTooLong, span, input) {
  if (isTooLong) {
    span.classList.add('too-long');
    input.classList.add('input-too-long');
  } else {
    span.classList.remove('too-long');
    input.classList.remove('input-too-long');
  }
}

/**
 * set text in a span
 * @param {string} text
 * @param {Element} span
 */
function setText(text, span) {
  span.innerHTML = text;
}

/**
 * set styles depending on the remaining length
 * @param {number} remaining
 * @param {Element} el
 */
function setStyles(remaining, el) {
  var input = getInput(el.parentNode),
    span = el;

  if (remaining > 0) {
    toggleClass(false, span, input);
    setText('Remaining: ' + remaining, span);
  } else if (remaining === 0) {
    toggleClass(false, span, input);
    setText('At the character limit', span);
  } else if (remaining === -1) {
    toggleClass(true, span, input);
    setText(-remaining + ' character over the limit', span);
  } else {
    toggleClass(true, span, input);
    setText(-remaining + ' characters over the limit', span);
  }
}

/**
 * Remove tags and white spaces.
 * @param {string} value
 * @returns {string}
 */
function cleanValue(value) {
  var clean = striptags(value);

  clean = clean.replace(/(\u00a0| | )/ig, ' '); // remove
  return clean;
}

/**
 * Add soft max length: appends el and adds formatter.
 * @param {{name: string, el: Element, bindings: {}, formatters: {}}} result
 * @param {{value: string}} args
 * @returns {{}}
 */
module.exports = function (result, args) {
  var el = result.el,
    name = result.name,
    tpl = `

    `,
    span = dom.create(tpl);

  result.binders.remaining = function (el, value) {
    var length = cleanValue(value).length,
      max = parseInt(el.getAttribute('data-maxlength')),
      remaining = max - length;

    setStyles(remaining, el);
  };

  el.appendChild(span);
  return result;
};
<commit_msg>better calculating of soft-maxlength character count<commit_after>var striptags = require('striptags'),
  he = require('he'),
  dom = require('../services/dom'),
  getInput = require('../services/get-input');

/**
 * toggle classes on elements
 * @param {boolean} isTooLong
 * @param {Element} span
 * @param {Element} input
 */
function toggleClass(isTooLong, span, input) {
  if (isTooLong) {
    span.classList.add('too-long');
    input.classList.add('input-too-long');
  } else {
    span.classList.remove('too-long');
    input.classList.remove('input-too-long');
  }
}

/**
 * set text in a span
 * @param {string} text
 * @param {Element} span
 */
function setText(text, span) {
  span.innerHTML = text;
}

/**
 * set styles depending on the remaining length
 * @param {number} remaining
 * @param {Element} el
 */
function setStyles(remaining, el) {
  var input = getInput(el.parentNode),
    span = el;

  if (remaining > 0) {
    toggleClass(false, span, input);
    setText('Remaining: ' + remaining, span);
  } else if (remaining === 0) {
    toggleClass(false, span, input);
    setText('At the character limit', span);
  } else if (remaining === -1) {
    toggleClass(true, span, input);
    setText(-remaining + ' character over the limit', span);
  } else {
    toggleClass(true, span, input);
    setText(-remaining + ' characters over the limit', span);
  }
}

/**
 * Remove tags and white spaces.
 * @param {string} value
 * @returns {string}
 */
function cleanValue(value) {
  var clean = he.decode(striptags(value));

  return clean;
}

/**
 * Add soft max length: appends el and adds formatter.
 * @param {{name: string, el: Element, bindings: {}, formatters: {}}} result
 * @param {{value: string}} args
 * @returns {{}}
 */
module.exports = function (result, args) {
  var el = result.el,
    name = result.name,
    tpl = `

    `,
    span = dom.create(tpl);

  result.binders.remaining = function (el, value) {
    var length = cleanValue(value).length,
      max = parseInt(el.getAttribute('data-maxlength')),
      remaining = max - length;

    setStyles(remaining, el);
  };

  el.appendChild(span);
  return result;
};
<|endoftext|>


<commit_before>var select = require('cheerio-soupselect').select;

function parse(dom) {
  var liftStatus = {},
    statusConverter = {
      'delay': 'scheduled',
      'open': 'open',
      'windhold': 'hold',
      'closed': 'closed'
    };

  // add parsing code here
  select(dom, '#lift_report-page td.lift').forEach(function(node) {
    var nameNode = node.next,
      name = nameNode.children[0].data.trim(),
      status = nameNode.next.children[0].attribs.src;
    status = status.slice(status.lastIndexOf('/') + 1, -4);
    liftStatus[name] = statusConverter[status] || 'scheduled';
  });

  return liftStatus;
}

module.exports = {
  name: 'Killington',
  url: {
    host: 'http://www.killington.com',
    pathname: '/winter/mountain/conditions/lifts'
  },
  parse: parse
};
<commit_msg>Add 'New England' tag<commit_after>var select = require('cheerio-soupselect').select;

function parse(dom) {
  var liftStatus = {},
    statusConverter = {
      'delay': 'scheduled',
      'open': 'open',
      'windhold': 'hold',
      'closed': 'closed'
    };

  // add parsing code here
  select(dom, '#lift_report-page td.lift').forEach(function(node) {
    var nameNode = node.next,
      name = nameNode.children[0].data.trim(),
      status = nameNode.next.children[0].attribs.src;
    status = status.slice(status.lastIndexOf('/') + 1, -4);
    liftStatus[name] = statusConverter[status] || 'scheduled';
  });

  return liftStatus;
}

module.exports = {
  name: 'Killington',
  url: {
    host: 'http://www.killington.com',
    pathname: '/winter/mountain/conditions/lifts'
  },
  tags: ['New England'],
  parse: parse
};
<|endoftext|>


<commit_before>const S_DIGITAL_CONSUMER_KEY = process.env['S_DIGITAL_CONSUMER_KEY'];
const S_DIGITAL_CONSUMER_SECRET = process.env['S_DIGITAL_CONSUMER_SECRET'];

const api = require('7digital-api').configure({
  consumerkey: S_DIGITAL_CONSUMER_KEY,
  consumersecret: S_DIGITAL_CONSUMER_SECRET,
  defaultParams: {
    country: 'de'
  }
});

var releases = new api.Releases();

module.exports = (item) => {
  return new Promise((resolve, reject) => {
    releases.search({q: item.title}, (error, data) => {
      if(error){
        reject(error);
        return;
      }
      resolve(data);
    });
  })
  .then(data => data.searchResults.searchResult);
};
<commit_msg>add 7digital restorer<commit_after>const btc = require('bloom-text-compare');

const S_DIGITAL_CONSUMER_KEY = process.env['S_DIGITAL_CONSUMER_KEY'];
const S_DIGITAL_CONSUMER_SECRET = process.env['S_DIGITAL_CONSUMER_SECRET'];

const api = require('7digital-api').configure({
  consumerkey: S_DIGITAL_CONSUMER_KEY,
  consumersecret: S_DIGITAL_CONSUMER_SECRET,
  defaultParams: {
    country: 'de'
  }
});

var releases = new api.Releases();

module.exports = (item) => {
  return new Promise((resolve, reject) => {
    releases.search({q: item.title}, (error, data) => {
      if(error){
        reject(error);
        return;
      }
      resolve(data);
    });
  })
  .then(data => data.searchResults.searchResult)
  .then((results) => {
    if(!results.length){ return null; }
    var item_title = item.title.toLowerCase();

    var s_digital_item = results[0].release;
    var s_digital_title = `${s_digital_item.artist.name} - ${s_digital_item.title}`.toLowerCase();
    var similarity;
    if(item_title === s_digital_title){
      similarity = 1;
    } else {
      var hash1 = btc.hash(s_digital_title.split(' '));
      var hash2 = btc.hash(item_title.split(' '));
      var similarity = btc.compare(hash1, hash2);
      if(similarity < 0.5){ return null; }
    }
    return {
      track_count: s_digital_item.trackCount,
      price: s_digital_item.price.value,
      currency: s_digital_item.price.currency.code,
      artist: s_digital_item.artist.name,
      album: s_digital_item.title,
      similarity: similarity,
      id: s_digital_item.id
    }
  });
};
<|endoftext|>


<commit_before>"use strict";

var utils = require('../utils');

module.exports = function (app) {
  utils.requires(app, 'routes/db');

  // Put a document attachment
  function putAttachment(db, name, req, res) {
    var attachment = req.params.attachment,
        rev = req.query.rev,
        type = req.get('Content-Type') || 'application/octet-stream',
        body = new Buffer(req.rawBody || '', 'binary'),
        opts = utils.makeOpts(req);

    function cb(err, response) {
      if (err) {
        return utils.sendError(res, err);
      }
      utils.setLocation(res, db + '/' + name + '/' + attachment);
      utils.sendJSON(res, 201, response);
    }
    req.db.putAttachment(name, attachment, rev, body, type, opts, cb);
  }

  app.put('/:db/_design/:id/:attachment(*)', utils.parseRawBody,
    function (req, res, next) {
      putAttachment(req.params.db, '_design/' + req.params.id, req, res);
    }
  );

  app.put('/:db/:id/:attachment(*)', utils.parseRawBody,
    function (req, res, next) {
      // Be careful not to catch normal design docs or local docs
      if (req.params.id === '_design' || req.params.id === '_local') {
        return next();
      }
      putAttachment(req.params.db, req.params.id, req, res);
    }
  );

  // Retrieve a document attachment
  function getAttachment(name, req, res) {
    var attachment = req.params.attachment;
    var opts = utils.makeOpts(req, req.query);

    req.db.get(name, opts, function (err, info) {
      if (err) {
        return utils.sendError(res, err);
      }

      if (!info._attachments || !info._attachments[attachment]) {
        return utils.sendJSON(res, 404, {
          error: 'not_found',
          reason: 'missing'
        });
      }

      var type = info._attachments[attachment].content_type;
      var md5 = info._attachments[attachment].digest.slice(4);

      req.db.getAttachment(name, attachment, function (err, response) {
        if (err) {
          return utils.sendError(res, err);
        }
        res.set('ETag', '"' + md5 + '"');
        res.setHeader('Content-Type', type);
        res.status(200).send(response);
      });
    });
  }

  app.get('/:db/_design/:id/:attachment(*)', function (req, res, next) {
    getAttachment('_design/' + req.params.id, req, res);
  });

  app.get('/:db/:id/:attachment(*)', function (req, res, next) {
    // Be careful not to catch normal design docs or local docs
    if (req.params.id === '_design' || req.params.id === '_local') {
      return next();
    }
    getAttachment(req.params.id, req, res);
  });

  // Delete a document attachment
  function deleteAttachment(name, req, res) {
    var attachment = req.params.attachment,
        rev = req.query.rev,
        opts = utils.makeOpts(req);

    function cb(err, response) {
      if (err) {
        return utils.sendError(res, err);
      }
      utils.sendJSON(res, 200, response);
    }
    req.db.removeAttachment(name, attachment, rev, opts, cb);
  }

  app.delete('/:db/_design/:id/:attachment(*)', function (req, res, next) {
    deleteAttachment('_design/' + req.params.id, req, res);
  });

  app.delete('/:db/:id/:attachment(*)', function (req, res, next) {
    // Be careful not to catch normal design docs or local docs
    if (req.params.id === '_design' || req.params.id === '_local') {
      return next();
    }
    deleteAttachment(req.params.id, req, res);
  });
};
<commit_msg>(#172) - Attachment couchdb-harness fixes<commit_after>"use strict";

var utils = require('../utils');

module.exports = function (app) {
  utils.requires(app, 'routes/db');

  // Put a document attachment
  function putAttachment(db, name, req, res) {
    var attachment = req.params.attachment,
        rev = req.query.rev,
        type = req.get('Content-Type') || 'application/octet-stream',
        body = new Buffer(req.rawBody || '', 'binary'),
        opts = utils.makeOpts(req);

    function cb(err, response) {
      if (err) {
        return utils.sendError(res, err);
      }
      res.set('ETag', JSON.stringify(response.rev));
      var attachmentURI = encodeURIComponent(attachment);
      utils.setLocation(res, db + '/' + name + '/' + attachmentURI);
      utils.sendJSON(res, 201, response);
    }
    req.db.putAttachment(name, attachment, rev, body, type, opts, cb);
  }

  app.put('/:db/_design/:id/:attachment(*)', utils.parseRawBody,
    function (req, res, next) {
      putAttachment(req.params.db, '_design/' + req.params.id, req, res);
    }
  );

  app.put('/:db/:id/:attachment(*)', utils.parseRawBody,
    function (req, res, next) {
      // Be careful not to catch normal design docs or local docs
      if (req.params.id === '_design' || req.params.id === '_local') {
        return next();
      }
      putAttachment(req.params.db, req.params.id, req, res);
    }
  );

  // Retrieve a document attachment
  function getAttachment(name, req, res) {
    var attachment = req.params.attachment;
    var opts = utils.makeOpts(req, req.query);

    req.db.get(name, opts, function (err, info) {
      if (err) {
        return utils.sendError(res, err);
      }

      if (!info._attachments || !info._attachments[attachment]) {
        return utils.sendJSON(res, 404, {
          error: 'not_found',
          reason: 'missing'
        });
      }

      var type = info._attachments[attachment].content_type;
      var md5 = info._attachments[attachment].digest.slice(4);

      req.db.getAttachment(name, attachment, function (err, response) {
        if (err) {
          return utils.sendError(res, err);
        }
        res.set('ETag', JSON.stringify(md5));
        res.setHeader('Content-Type', type);
        res.status(200).send(response);
      });
    });
  }

  app.get('/:db/_design/:id/:attachment(*)', function (req, res, next) {
    getAttachment('_design/' + req.params.id, req, res);
  });

  app.get('/:db/:id/:attachment(*)', function (req, res, next) {
    // Be careful not to catch normal design docs or local docs
    if (req.params.id === '_design' || req.params.id === '_local') {
      return next();
    }
    getAttachment(req.params.id, req, res);
  });

  // Delete a document attachment
  function deleteAttachment(name, req, res) {
    var attachment = req.params.attachment,
        rev = req.query.rev,
        opts = utils.makeOpts(req);

    function cb(err, response) {
      if (err) {
        return utils.sendError(res, err);
      }
      utils.sendJSON(res, 200, response);
    }
    req.db.removeAttachment(name, attachment, rev, opts, cb);
  }

  app.delete('/:db/_design/:id/:attachment(*)', function (req, res, next) {
    deleteAttachment('_design/' + req.params.id, req, res);
  });

  app.delete('/:db/:id/:attachment(*)', function (req, res, next) {
    // Be careful not to catch normal design docs or local docs
    if (req.params.id === '_design' || req.params.id === '_local') {
      return next();
    }
    deleteAttachment(req.params.id, req, res);
  });
};
<|endoftext|>


<commit_before>/**
 * @param {string} propertyName
 * @param {{scope: string!, scopes: Object.<string, *>!}} config
 * @returns {Object.<string, *>} - values mapped by package name
 */
const getScopedValues = (propertyName, config) => {
  if (!(config.scope in config.scopes)) {
    throw new Error(`Unknown scope \`${ config.scope }\`. Known scopes are [${
      Object.keys(config.scopes).join('./')
      }]. Mind that scopes have been normalised.`)
  }

  /**
   * @type {object|null}
   */
  const scopedConfig = config.scopes[config.scope];

  return scopedConfig && propertyName in scopedConfig ?
    scopedConfig[propertyName] :
    {};
};

/**
 * @param {string} propertyName
 * @param {{[propertyName]: Object.<string, *>?}} config
 * @returns {Object.<string, *>} - values mapped by package name
 */
const getCommonValues = (propertyName, config) => config[propertyName] || {};

module.exports = {
  compile: (propertyName, config) => Object.assign({},
    getCommonValues(propertyName, config),
    getScopedValues(propertyName, config)
  ),
};
<commit_msg>Fix: allow not having `scopes` in config<commit_after>/**
 * @param {string} propertyName
 * @param {{scope: string!, scopes: Object.<string, *>!}} config
 * @returns {Object.<string, *>} - values mapped by package name
 */
const getScopedValues = (propertyName, config) => {
  if (!('scopes' in config)) {
    return {};
  }

  if (!(config.scope in config.scopes)) {
    throw new Error(`Unknown scope \`${ config.scope }\`. Known scopes are [${
      Object.keys(config.scopes).join('./')
      }]. Mind that scopes have been normalised.`)
  }

  /**
   * @type {object|null}
   */
  const scopedConfig = config.scopes[config.scope];

  return scopedConfig && propertyName in scopedConfig ?
    scopedConfig[propertyName] :
    {};
};

/**
 * @param {string} propertyName
 * @param {{[propertyName]: Object.<string, *>?}} config
 * @returns {Object.<string, *>} - values mapped by package name
 */
const getCommonValues = (propertyName, config) => config[propertyName] || {};

module.exports = {
  compile: (propertyName, config) => Object.assign({},
    getCommonValues(propertyName, config),
    getScopedValues(propertyName, config)
  ),
};
<|endoftext|>


<commit_before>/**
 * Created by moustafabaiou on 3/16/17.
 */
(function () {
    'use strict';

    angular
        .module('vbr-style-guide')
        .controller('ExampleController',

            function ExampleController(BannerService) {
                var ex = this;

                ex.someValue = {
                    firstThing: 'thing to be passed'
                };

                //input example
                ex.someModel = '';
                ex.someValidations = {
                    'email': {
                        message: 'Email is invalid'
                    },
                    'minlength': {
                        message: 'This is an example minlength error'
                    },
                    'maxlength': {
                        message: 'This is an example maxlength error'
                    },
                    'pattern': {
                        message: 'This is an example pattern error'
                    }
                };
                ex.disabled = false;
                ex.disableInput = function () {
                    ex.disabled = !ex.disabled;
                };

                ex.dropdownOptions = [
                    {display: 'option 1', value: 1},
                    {display: 'option 2', value: 2},
                    {display: 'option 3', value: 3},
                    {display: 'option 4', value: 4}
                ];

                ex.disableDropdown = function () {
                    ex.disabledDropdown = !ex.disabledDropdown;
                };

                ex.someFunction = function () {
                    console.log('change function called');
                };

                ex.tooltipTesting = 'Some instructions for the input';

                ex.somePhotoModel = null;
                ex.photoValidations = {
                    'required': {
                        message: 'please add a photo'
                    },
                    'pattern': {
                        message: 'This file must be an image'
                    },
                    'maxSize': {
                        message: 'The file is too big'
                    }
                };

                ex.errorBanner = {
                    type: 'error',
                    message: 'Some error occured here!',
                    autoClose: false,
                    onCloseFn: function (args) {
                        console.log('banner close callback fn fired: ' + args.firstThing);
                    },
                    animated: true,
                    animationDuration: 3000,
                    animationName: 'squeeze-up'
                };

                ex.alert = function () {
                  console.log("BLUR");
                };

                ex.successBanner = {
                    type: 'success',
                    message: 'Congratulations, your request is a yuuuuge success',
                    autoClose: false,
                    onCloseFn: function (args) {
                        console.log('banner close callback fn fired: ' + args.firstThing);
                    },
                    animated: true,
                    animationDuration: 3000,
                    animationName: 'squeeze-up'
                };

                ex.checkboxDisabled = false;

                ex.disableCheckbox = function () {
                    ex.checkboxDisabled = !ex.checkboxDisabled;
                };

                ex.checkedCallback = function (option) {
                    console.log('the checkbox callback is being called for option:', option);
                };

                ex.fruits =
                    [
                        {
                            value:'apple',
                            checked: false
                        },
                        {
                            value: 'banana',
                            checked: false
                        },
                        {
                            value: 'pear',
                            checked: true
                        }
                    ];

                ex.vegetables = [
                    {
                        displayValue: 'apple',
                        value: 'ACS12345',
                        checked: false
                    },
                    {
                        displayValue: 'banana',
                        value: 'a;lskdf;',
                        checked: true
                    },
                    {
                        displayValue: 'pear',
                        value: 'sjvjs',
                        checked: false
                    }
                ];

                ex.radioOptions = [
                    'option 1',
                    'option 2',
                    'option 3'
                ];

                ex.radioDisabled = false;

                ex.disableRadio = function () {
                    ex.radioDisabled = !ex.radioDisabled;
                };

                ex.radioModel = null;

                ex.hideBanner = function (name) {
                    BannerService.set(name,{hidden:true});
                };

                ex.clickNum = 0;

                ex.alertfn = function () {
                    alert("adsdas");
                };

                ex.showBanner = function (name) {

                    var configurationObj = {
                        visible: true,
                        message: "dsaadsasd" + " " + name + " " + "clicked:",
                        icon: "icon_vibrent_check",
                        type: "success",
                        hiddenCallback: function () {
                            alert("Im hidden");
                        },
                        shownCallback: function () {
                            alert("Im Shown");
                        },
                        visibilityDuration: Infinity,
                        cssClassList: ""
                    };
                    ex.clickNum++;
                    BannerService.set(name,configurationObj);
                };

                ex.radioChange = function(option){
                    console.log('the radio callback is being called for option:', option);
                };

            });
})();

<commit_msg>Updates to code readability<commit_after>/**
 * Created by moustafabaiou on 3/16/17.
 */
(function () {
    'use strict';

    angular
        .module('vbr-style-guide')
        .controller('ExampleController',

            function ExampleController(BannerService) {
                var ex = this;

                ex.someValue = {
                    firstThing: 'thing to be passed'
                };

                //input example
                ex.someModel = '';
                ex.someValidations = {
                    'email': {
                        message: 'Email is invalid'
                    },
                    'minlength': {
                        message: 'This is an example minlength error'
                    },
                    'maxlength': {
                        message: 'This is an example maxlength error'
                    },
                    'pattern': {
                        message: 'This is an example pattern error'
                    }
                };
                ex.disabled = false;
                ex.disableInput = function () {
                    ex.disabled = !ex.disabled;
                };

                ex.dropdownOptions = [
                    {display: 'option 1', value: 1},
                    {display: 'option 2', value: 2},
                    {display: 'option 3', value: 3},
                    {display: 'option 4', value: 4}
                ];

                ex.disableDropdown = function () {
                    ex.disabledDropdown = !ex.disabledDropdown;
                };

                ex.someFunction = function () {
                    console.log('change function called');
                };

                ex.tooltipTesting = 'Some instructions for the input';

                ex.somePhotoModel = null;
                ex.photoValidations = {
                    'required': {
                        message: 'please add a photo'
                    },
                    'pattern': {
                        message: 'This file must be an image'
                    },
                    'maxSize': {
                        message: 'The file is too big'
                    }
                };

                ex.errorBanner = {
                    type: 'error',
                    message: 'Some error occured here!',
                    autoClose: false,
                    onCloseFn: function (args) {
                        console.log('banner close callback fn fired: ' + args.firstThing);
                    },
                    animated: true,
                    animationDuration: 3000,
                    animationName: 'squeeze-up'
                };

                ex.alert = function () {
                  console.log("BLUR");
                };

                ex.successBanner = {
                    type: 'success',
                    message: 'Congratulations, your request is a yuuuuge success',
                    autoClose: false,
                    onCloseFn: function (args) {
                        console.log('banner close callback fn fired: ' + args.firstThing);
                    },
                    animated: true,
                    animationDuration: 3000,
                    animationName: 'squeeze-up'
                };

                ex.checkboxDisabled = false;

                ex.disableCheckbox = function () {
                    ex.checkboxDisabled = !ex.checkboxDisabled;
                };

                ex.checkedCallback = function (option) {
                    console.log('the checkbox callback is being called for option:', option);
                };

                ex.fruits =
                    [
                        {
                            value:'apple',
                            checked: false
                        },
                        {
                            value: 'banana',
                            checked: false
                        },
                        {
                            value: 'pear',
                            checked: true
                        }
                    ];

                ex.vegetables = [
                    {
                        displayValue: 'apple',
                        value: 'ACS12345',
                        checked: false
                    },
                    {
                        displayValue: 'banana',
                        value: 'a;lskdf;',
                        checked: true
                    },
                    {
                        displayValue: 'pear',
                        value: 'sjvjs',
                        checked: false
                    }
                ];

                ex.radioOptions = [
                    'option 1',
                    'option 2',
                    'option 3'
                ];

                ex.radioDisabled = false;

                ex.disableRadio = function () {
                    ex.radioDisabled = !ex.radioDisabled;
                };

                ex.radioModel = null;

                ex.hideBanner = function (name) {
                    BannerService.set(name,{hidden:true});
                };

                ex.clickNum = 0;

                ex.alertfn = function () {
                    alert("This is a recompiled alert");
                };

                ex.showBanner = function (name) {

                    var configurationObj = {
                        visible: true,
                        message: "ALERT ME!" + " " + name + " " + "clicked:",
                        icon: "icon_vibrent_check",
                        type: "success",
                        hiddenCallback: function () {
                            alert("Im hidden");
                        },
                        shownCallback: function () {
                            alert("Im Shown");
                        },
                        visibilityDuration: Infinity,
                        cssClassList: ""
                    };
                    ex.clickNum++;
                    BannerService.set(name,configurationObj);
                };

                ex.radioChange = function(option){
                    console.log('the radio callback is being called for option:', option);
                };

            });
})();

<|endoftext|>