<commit_before>var fs = require('fs');
var path = require('path');
var slug = require('slug');

var pjson = JSON.parse(fs.readFileSync('package.json', 'utf8'));

var date2str = function (x, y) {
  var z = {
    M: x.getMonth() + 1,
    d: x.getDate(),
    h: x.getHours(),
    m: x.getMinutes(),
    s: x.getSeconds()
  };
  y = y.replace(/(M+|d+|h+|m+|s+)/g, function(v) {
    return ((v.length > 1 ? "0" : "") + eval('z.' + v.slice(-1))).slice(-2)
  });

  return y.replace(/(y+)/g, function(v) {
    return x.getFullYear().toString().slice(-v.length)
  });
}

var collection, data;
var seeder_file = path.join(__dirname, '../city-council/data/' + date2str(new Date(), 'yyyyMMddhhmmss') + '-city-council-' + slug(pjson.cityData.state_code, { lower: true, replacement: '-' }) + '-' + slug(pjson.cityData.city_name, { lower: true, replacement: '-' }) + '-seeder.js');
var data_file = path.join(__dirname, '../city-council/data/city-council-data.json');

function createSeeder() {
  var seeder = "module.exports = {\n" +
  "  up: function (queryInterface) {\n" +
  "    return queryInterface.bulkInsert('city_council', " +
  JSON.stringify(collection, null, 4) +
  ", {\n" +
  "      updateOnDuplicate: [ 'state_name','state_name_slug','state_code','state_code_slug','city_name','city_name_slug','district','at_large','vacant','title','party','name','name_slug','first_name','middle_name','last_name','name_suffix','goes_by','pronunciation','gender','ethnicity','date_of_birth','entered_office','term_end','email','phone','latitude','longitude','address_complete','address_number','address_prefix','address_street','address_sec_unit_type','address_sec_unit_num','address_city','address_state','address_zipcode','address_type','population','background_url','city_government_url','city_council_url','city_council_calendar_url','city_council_legislation_url','city_council_committees_url','twitter_handle','twitter_url','facebook_url','photo_url','modified_date','shape' ]\n" +
  "    }).catch(function (err) {\n" +
  "      if (err && err.errors) {\n" +
  "        for (var i = 0; i < err.errors.length; i++) {\n" +
  "          console.error('× SEED ERROR', err.errors[ i ].type, err.errors[ i ].message, err.errors[ i ].path, err.errors[ i ].value);\n" +
  "        }\n" +
  "      } else if (err && err.message) {\n" +
  "        console.error('× SEED ERROR', err.message);\n" +
  "      }\n" +
  "    });\n" +
  "  },\n" +
  "    down: function (queryInterface) {\n" +
  "    return queryInterface.bulkDelete('city_council', null, {});\n" +
  "  }" +
  "};\n";

  seeder = seeder.replace(/"queryInterface/g, 'queryInterface');
  seeder = seeder.replace(/}'\)"/g, '}\')');
  seeder = seeder.replace(/"new Date\(\)"/g, 'new Date()');
  seeder = seeder.replace(/"([a-z_]+)":/g, '$1:');
  seeder = seeder.replace(/: "true"/g, ': true');
  seeder = seeder.replace(/: "false"/g, ': false');
  seeder = seeder.replace(/\\"type\\":/g, '"type":');
  seeder = seeder.replace(/\\"MultiPolygon\\"/g, '"MultiPolygon"');
  seeder = seeder.replace(/\\"Polygon\\"/g, '"Polygon"');
  seeder = seeder.replace(/\\"coordinates\\":/g, '"coordinates":');

  fs.writeFile(seeder_file, seeder);
}

if (!fs.existsSync(data_file)) {
  console.error('× Missing JSON Data: ' + data_file.replace(path.join(__dirname, '../'), './'));
} else {

  collection = [];
  data = JSON.parse(fs.readFileSync(data_file, 'utf8'));

  for (var i = 0; i < data.length; i++) {

    var geojsonFile = 'city-council/geojson/city-council-';

    if (data[i].district) {
      geojsonFile += data[i].state_code_slug + '-' + data[i].city_name_slug + '-' + data[i].district.replace(/\s/g, '-').toLowerCase() + '.geojson';
    } else {
      geojsonFile += data[i].state_code_slug + '.geojson';
    }

    var contents = fs.readFileSync(path.join(__dirname, '../' + geojsonFile), 'utf8');
    var geojson = JSON.parse(contents);

    data[i].shape = 'queryInterface.sequelize.fn(\'ST_GeomFromGeoJSON\', \'' + JSON.stringify(geojson.geometry) + '\')'
    data[i].created_date = 'new Date()';
    data[i].modified_date = 'new Date()';
    collection.push(data[i]);
  }

  createSeeder();

  console.log('\n☆ Seeder Creation Completed ' + '\n');
}
<commit_msg>Updates to handle DeprecationWarning<commit_after>var fs = require('fs');
var path = require('path');
var slug = require('slug');

var pjson = JSON.parse(fs.readFileSync('package.json', 'utf8'));

var date2str = function (x, y) {
  var z = {
    M: x.getMonth() + 1,
    d: x.getDate(),
    h: x.getHours(),
    m: x.getMinutes(),
    s: x.getSeconds()
  };
  y = y.replace(/(M+|d+|h+|m+|s+)/g, function(v) {
    return ((v.length > 1 ? "0" : "") + eval('z.' + v.slice(-1))).slice(-2)
  });

  return y.replace(/(y+)/g, function(v) {
    return x.getFullYear().toString().slice(-v.length)
  });
}

var collection, data;
var seeder_file = path.join(__dirname, '../city-council/data/' + date2str(new Date(), 'yyyyMMddhhmmss') + '-city-council-' + slug(pjson.cityData.state_code, { lower: true, replacement: '-' }) + '-' + slug(pjson.cityData.city_name, { lower: true, replacement: '-' }) + '-seeder.js');
var data_file = path.join(__dirname, '../city-council/data/city-council-data.json');

function createSeeder() {
  var seeder = "module.exports = {\n" +
  "  up: function (queryInterface) {\n" +
  "    return queryInterface.bulkInsert('city_council', " +
  JSON.stringify(collection, null, 4) +
  ", {\n" +
  "      updateOnDuplicate: [ 'state_name','state_name_slug','state_code','state_code_slug','city_name','city_name_slug','district','at_large','vacant','title','party','name','name_slug','first_name','middle_name','last_name','name_suffix','goes_by','pronunciation','gender','ethnicity','date_of_birth','entered_office','term_end','email','phone','latitude','longitude','address_complete','address_number','address_prefix','address_street','address_sec_unit_type','address_sec_unit_num','address_city','address_state','address_zipcode','address_type','population','background_url','city_government_url','city_council_url','city_council_calendar_url','city_council_legislation_url','city_council_committees_url','twitter_handle','twitter_url','facebook_url','photo_url','modified_date','shape' ]\n" +
  "    }).catch(function (err) {\n" +
  "      if (err && err.errors) {\n" +
  "        for (var i = 0; i < err.errors.length; i++) {\n" +
  "          console.error('× SEED ERROR', err.errors[ i ].type, err.errors[ i ].message, err.errors[ i ].path, err.errors[ i ].value);\n" +
  "        }\n" +
  "      } else if (err && err.message) {\n" +
  "        console.error('× SEED ERROR', err.message);\n" +
  "      }\n" +
  "    });\n" +
  "  },\n" +
  "    down: function (queryInterface) {\n" +
  "    return queryInterface.bulkDelete('city_council', null, {});\n" +
  "  }" +
  "};\n";

  seeder = seeder.replace(/"queryInterface/g, 'queryInterface');
  seeder = seeder.replace(/}'\)"/g, '}\')');
  seeder = seeder.replace(/"new Date\(\)"/g, 'new Date()');
  seeder = seeder.replace(/"([a-z_]+)":/g, '$1:');
  seeder = seeder.replace(/: "true"/g, ': true');
  seeder = seeder.replace(/: "false"/g, ': false');
  seeder = seeder.replace(/\\"type\\":/g, '"type":');
  seeder = seeder.replace(/\\"MultiPolygon\\"/g, '"MultiPolygon"');
  seeder = seeder.replace(/\\"Polygon\\"/g, '"Polygon"');
  seeder = seeder.replace(/\\"coordinates\\":/g, '"coordinates":');

  fs.writeFile(seeder_file, seeder, function(){});
}

if (!fs.existsSync(data_file)) {
  console.error('× Missing JSON Data: ' + data_file.replace(path.join(__dirname, '../'), './'));
} else {

  collection = [];
  data = JSON.parse(fs.readFileSync(data_file, 'utf8'));

  for (var i = 0; i < data.length; i++) {

    if (data[i].at_large === true || data[i].at_large === 'true') {
      continue;
    }

    var geojsonFile = 'city-council/geojson/city-council-';

    if (data[i].district) {
      geojsonFile += data[i].state_code_slug + '-' + data[i].city_name_slug + '-' + data[i].district.replace(/\s/g, '-').toLowerCase() + '.geojson';
    } else {
      geojsonFile += data[i].state_code_slug + '.geojson';
    }

    var contents = fs.readFileSync(path.join(__dirname, '../' + geojsonFile), 'utf8');
    var geojson = JSON.parse(contents);

    data[i].shape = 'queryInterface.sequelize.fn(\'ST_GeomFromGeoJSON\', \'' + JSON.stringify(geojson.geometry) + '\')'
    data[i].created_date = 'new Date()';
    data[i].modified_date = 'new Date()';
    collection.push(data[i]);
  }

  createSeeder();

  console.log('\n☆ Seeder Creation Completed ' + '\n');
}
<|endoftext|>


<commit_before>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License (version 3) as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import Fuse from 'fuse.js';
// import ElasticLunr from 'elasticlunr';
import jmespath from 'jmespath';
import AppConfig from '../config';
import { Pro } from '../pro';

let currentQuery = '';
let nextQuery = '';
let caseSensitiveSearch = false; // TODO implement case sensitive search

// export type FileTypeGroups = 'images' | 'notes' | 'documents' | 'audio' | 'video' | 'archives';

export const FileTypeGroups = {
  any: [''],
  images: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif', 'tiff', 'ico', 'webp', 'psd'],
  notes: ['md', 'mdown', 'txt', 'html'],
  documents: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'odt', 'ods'],
  audio: ['ogg', 'mp3', 'wav', 'wave'],
  video: ['ogv', 'mp4', 'webm', 'm4v', 'mkv', 'avi'],
  archives: ['zip', 'rar', 'gz', 'tgz', 'arc', '7z'],
  bookmarks: ['url', 'lnk', 'sym'],
  ebooks: ['epub', 'mobi', 'azw', 'prc', 'azw1', 'azw3', 'azw4', 'azw8', 'azk'],
  folders: ['folders'],
  files: ['files'],
  untagged: ['untagged'],
};

export type SearchQuery = {
  textQuery?: string,
  fileTypes?: Array<string>,
  tagsAND?: Array<Tag>,
  tagsOR?: Array<Tag>,
  tagsNOT?: Array<Tag>,
  lastModified: string,
  fileSize: string,
  searchBoxing: 'location' | 'folder',
  currentDirectory: string,
  maxSearchResults?: number
};

// id, name, isFile, tags, extension, size, lmdt, path

// index[?size > '9000'  && contains(path, 'Apple')]
// index[?size < '2000' || isFile=='false']
// index[?contains(path, 'testfiles')].[tags[?title=='todo']
// index[].[tags[?type=='sidecar']]
// index[?tags[?type=='sidecar']]
// index[?tags[?title=='todo' || title=='high' ]

/*
  {
    "description": "",
    "extension": "jpg",
    "isFile": true,
    "lmdt": 1479679132839,
    "name": "Apple-Notes-Mac2 (copy).jpg",
    "path":
      "/home/na/TagSpaces/testfiles/sidecarTests/Apple-Notes-Mac2 (copy).jpg",
    "size": 84162,
    "tags": [
      {
        "style":
          "color: #333333 !important; background-color: #ffad46 !important;",
        "title": "todo",
        "type": "sidecar"
      }
    ],
    "thumbPath":
      "/home/na/TagSpaces/testfiles/sidecarTests/.ts/Apple-Notes-Mac2 (copy).jpg.jpg",
    "uuid": "76075fc2-4a2f-4e27-a188-d96fece032aa"
  },
*/

const fuseOptions = {
  shouldSort: true,
  threshold: 0.3,
  location: 0,
  distance: 100,
  tokenize: true,
  maxPatternLength: 32,
  minMatchCharLength: 1,
  keys: [{
    name: 'name',
    weight: 0.4
  }, {
    name: 'description',
    weight: 0.4
  }, {
    name: 'textContent',
    weight: 0.4
  }, {
    name: 'tags',
    weight: 0.3
  }, {
    name: 'path', // TODO ignore .ts folder, should not be in the index
    weight: 0.1
  }]
};

// Constructs the string for the JMESPath search query.  We first filter for all ORTags, then continuously pipe the result in to the
// filters for all AND and NOT tags. The Pro version can pipe the result into an additional filter for extension instead of tags.title.
// The final string for the tag search should look like this:
// index[? tags[? title=='ORTag1' || title=='ORTag2']] | [? tags[? title=='ANDTag1']] | [? tags[? title=='ANDTag2']] | [?!(tags[? title=='NOTTag1'])] | [?!(tags[? title=='NOTTag2'])] | extensionFilter
function constructjmespathQuery(searchQuery: SearchQuery): string {
  let jmespathQuery = '';
  const ANDtagsExist = searchQuery.tagsAND && searchQuery.tagsAND.length >= 1;
  const ORtagsExist = searchQuery.tagsOR && searchQuery.tagsOR.length >= 1;
  const NOTtagsExist = searchQuery.tagsNOT && searchQuery.tagsNOT.length >= 1;
  if (ANDtagsExist || ORtagsExist || NOTtagsExist) {
    jmespathQuery = '[?';

    if (ORtagsExist) {
      jmespathQuery += ' tags[? ';
      searchQuery.tagsOR.forEach(tag => {
        const cleanedTagTitle = tag.title.trim(); // .toLowerCase();
        if (cleanedTagTitle.length > 0) {
          jmespathQuery += 'title==\'' + cleanedTagTitle + '\' || ';
        }
        return true;
      });
    }

    if (ANDtagsExist) {
      if (jmespathQuery.endsWith(' || ')) {
        jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 4) + ']] | [? tags[? ';
      } else {
        jmespathQuery += ' tags[? ';
      }
      searchQuery.tagsAND.forEach(tag => {
        const cleanedTagTitle = tag.title.trim(); // .toLowerCase();
        if (cleanedTagTitle.length > 0) {
          jmespathQuery += 'title==\'' + cleanedTagTitle + '\']] | [? tags[? ';
        }
      });
    }

    if (NOTtagsExist) {
      if (jmespathQuery.endsWith(' || ')) {
        jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 4) + ']] | [?!(tags[? ';
      } else if (jmespathQuery.endsWith('[? tags[? ')) {
        jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 10) + '[?!(tags[? ';
      } else {
        jmespathQuery += '!(tags[? ';
      }
      searchQuery.tagsNOT.forEach(tag => {
        const cleanedTagTitle = tag.title.trim(); // .toLowerCase();
        if (cleanedTagTitle.length > 0) {
          jmespathQuery += 'title==\'' + cleanedTagTitle + '\'])] | [?!(tags[? ';
        }
        return true;
      });
    }

    if (jmespathQuery.endsWith(' || ')) {
      jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 4) + ']]';
    } else if (jmespathQuery.endsWith(' | [? tags[? ')) {
      jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 13);
    } else if (jmespathQuery.endsWith(' | [?!(tags[? ')) {
      jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 14);
    }
  }

  const extensionQuery = Pro ? Pro.Search.constructFileTypeQuery(searchQuery) : '';
  if (extensionQuery.length > 0) {
    jmespathQuery = (jmespathQuery.length > 0) ? jmespathQuery + ' | ' + extensionQuery : extensionQuery;
  }

  if (jmespathQuery.length > 0) {
    return 'index' + jmespathQuery;
  }
  return jmespathQuery;
}

export default class Search {
  static searchLocationIndex = (locationContent: Array<Object>, searchQuery: SearchQuery): Promise<Array<Object> | []> => new Promise((resolve) => {
    console.time('searchtime');
    const jmespathQuery = constructjmespathQuery(searchQuery);
    let jmespathResults;
    let results;
    let currentDirectoryEntries;

    if (searchQuery.searchBoxing === 'folder') {
      currentDirectoryEntries = locationContent.filter(entry => entry.path.startsWith(searchQuery.currentDirectory));
    }

    if (jmespathQuery) {
      console.log('jmespath query: ' + jmespathQuery);
      console.time('jmespath');
      jmespathResults = jmespath.search({ index: currentDirectoryEntries || locationContent }, jmespathQuery);
      console.timeEnd('jmespath');
      console.log('jmespath results: ' + jmespathResults.length);
    }

    // if (Pro && Pro.Search.filterIndex) {
    //   results = Pro.Search.filterIndex(results || locationContent, searchQuery);
    // }

    if (searchQuery.textQuery && searchQuery.textQuery.length > 1) {
      console.log('fuse query: ' + searchQuery.textQuery);
      console.time('fuse');
      const fuse = new Fuse(jmespathResults || currentDirectoryEntries || locationContent, fuseOptions);
      results = fuse.search(searchQuery.textQuery);
      console.timeEnd('fuse');
    } else {
      results = jmespathResults;
    }

    if (results) {
      console.log('Results found: ' + results.length);
      if (searchQuery.maxSearchResults && results.length >= searchQuery.maxSearchResults) {
        results = results.slice(0, searchQuery.maxSearchResults);
      }
      console.log('Results send: ' + results.length);
      console.timeEnd('searchtime');
      resolve(results);
      return true;
    }
    results = [];
    console.timeEnd('searchtime');
    resolve(results);
  });
}
<commit_msg>making searching for tags case insensitive<commit_after>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License (version 3) as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import Fuse from 'fuse.js';
// import ElasticLunr from 'elasticlunr';
import jmespath from 'jmespath';
import AppConfig from '../config';
import { Pro } from '../pro';

let currentQuery = '';
let nextQuery = '';
let caseSensitiveSearch = false; // TODO implement case sensitive search

// export type FileTypeGroups = 'images' | 'notes' | 'documents' | 'audio' | 'video' | 'archives';

export const FileTypeGroups = {
  any: [''],
  images: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif', 'tiff', 'ico', 'webp', 'psd'],
  notes: ['md', 'mdown', 'txt', 'html'],
  documents: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'odt', 'ods'],
  audio: ['ogg', 'mp3', 'wav', 'wave'],
  video: ['ogv', 'mp4', 'webm', 'm4v', 'mkv', 'avi'],
  archives: ['zip', 'rar', 'gz', 'tgz', 'arc', '7z'],
  bookmarks: ['url', 'lnk', 'sym'],
  ebooks: ['epub', 'mobi', 'azw', 'prc', 'azw1', 'azw3', 'azw4', 'azw8', 'azk'],
  folders: ['folders'],
  files: ['files'],
  untagged: ['untagged'],
};

export type SearchQuery = {
  textQuery?: string,
  fileTypes?: Array<string>,
  tagsAND?: Array<Tag>,
  tagsOR?: Array<Tag>,
  tagsNOT?: Array<Tag>,
  lastModified: string,
  fileSize: string,
  searchBoxing: 'location' | 'folder',
  currentDirectory: string,
  maxSearchResults?: number
};

// id, name, isFile, tags, extension, size, lmdt, path

// index[?size > '9000'  && contains(path, 'Apple')]
// index[?size < '2000' || isFile=='false']
// index[?contains(path, 'testfiles')].[tags[?title=='todo']
// index[].[tags[?type=='sidecar']]
// index[?tags[?type=='sidecar']]
// index[?tags[?title=='todo' || title=='high' ]

/*
  {
    "description": "",
    "extension": "jpg",
    "isFile": true,
    "lmdt": 1479679132839,
    "name": "Apple-Notes-Mac2 (copy).jpg",
    "path":
      "/home/na/TagSpaces/testfiles/sidecarTests/Apple-Notes-Mac2 (copy).jpg",
    "size": 84162,
    "tags": [
      {
        "style":
          "color: #333333 !important; background-color: #ffad46 !important;",
        "title": "todo",
        "type": "sidecar"
      }
    ],
    "thumbPath":
      "/home/na/TagSpaces/testfiles/sidecarTests/.ts/Apple-Notes-Mac2 (copy).jpg.jpg",
    "uuid": "76075fc2-4a2f-4e27-a188-d96fece032aa"
  },
*/

const fuseOptions = {
  shouldSort: true,
  threshold: 0.3,
  location: 0,
  distance: 100,
  tokenize: true,
  maxPatternLength: 32,
  minMatchCharLength: 1,
  keys: [{
    name: 'name',
    weight: 0.4
  }, {
    name: 'description',
    weight: 0.4
  }, {
    name: 'textContent',
    weight: 0.4
  }, {
    name: 'tags',
    weight: 0.3
  }, {
    name: 'path', // TODO ignore .ts folder, should not be in the index
    weight: 0.1
  }]
};

// Constructs the string for the JMESPath search query.  We first filter for all ORTags, then continuously pipe the result in to the
// filters for all AND and NOT tags. The Pro version can pipe the result into an additional filter for extension instead of tags.title.
// The final string for the tag search should look like this:
// index[? tags[? title=='ORTag1' || title=='ORTag2']] | [? tags[? title=='ANDTag1']] | [? tags[? title=='ANDTag2']] | [?!(tags[? title=='NOTTag1'])] | [?!(tags[? title=='NOTTag2'])] | extensionFilter
function constructjmespathQuery(searchQuery: SearchQuery): string {
  let jmespathQuery = '';
  const ANDtagsExist = searchQuery.tagsAND && searchQuery.tagsAND.length >= 1;
  const ORtagsExist = searchQuery.tagsOR && searchQuery.tagsOR.length >= 1;
  const NOTtagsExist = searchQuery.tagsNOT && searchQuery.tagsNOT.length >= 1;
  if (ANDtagsExist || ORtagsExist || NOTtagsExist) {
    jmespathQuery = '[?';

    if (ORtagsExist) {
      jmespathQuery += ' tags[? ';
      searchQuery.tagsOR.forEach(tag => {
        const cleanedTagTitle = tag.title.trim().toLowerCase();
        if (cleanedTagTitle.length > 0) {
          jmespathQuery += 'title==\'' + cleanedTagTitle + '\' || ';
        }
        return true;
      });
    }

    if (ANDtagsExist) {
      if (jmespathQuery.endsWith(' || ')) {
        jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 4) + ']] | [? tags[? ';
      } else {
        jmespathQuery += ' tags[? ';
      }
      searchQuery.tagsAND.forEach(tag => {
        const cleanedTagTitle = tag.title.trim().toLowerCase();
        if (cleanedTagTitle.length > 0) {
          jmespathQuery += 'title==\'' + cleanedTagTitle + '\']] | [? tags[? ';
        }
      });
    }

    if (NOTtagsExist) {
      if (jmespathQuery.endsWith(' || ')) {
        jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 4) + ']] | [?!(tags[? ';
      } else if (jmespathQuery.endsWith('[? tags[? ')) {
        jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 10) + '[?!(tags[? ';
      } else {
        jmespathQuery += '!(tags[? ';
      }
      searchQuery.tagsNOT.forEach(tag => {
        const cleanedTagTitle = tag.title.trim().toLowerCase();
        if (cleanedTagTitle.length > 0) {
          jmespathQuery += 'title==\'' + cleanedTagTitle + '\'])] | [?!(tags[? ';
        }
        return true;
      });
    }

    if (jmespathQuery.endsWith(' || ')) {
      jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 4) + ']]';
    } else if (jmespathQuery.endsWith(' | [? tags[? ')) {
      jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 13);
    } else if (jmespathQuery.endsWith(' | [?!(tags[? ')) {
      jmespathQuery = jmespathQuery.substring(0, jmespathQuery.length - 14);
    }
  }

  const extensionQuery = Pro ? Pro.Search.constructFileTypeQuery(searchQuery) : '';
  if (extensionQuery.length > 0) {
    jmespathQuery = (jmespathQuery.length > 0) ? jmespathQuery + ' | ' + extensionQuery : extensionQuery;
  }

  if (jmespathQuery.length > 0) {
    return 'index' + jmespathQuery;
  }
  return jmespathQuery;
}

function transformTagsToLowercase(index: Array<Object>) {
  console.time('taglowercase');
  const resultIndex = index.map((entry) => {
    const tags = [...entry.tags];
    if (tags && tags.length) {
      tags.map((tag) => {
        tag.title = tag.title.toLowerCase();
        return tag;
      });
    }
    return {
      ...entry,
      tags
    };
  });
  console.timeEnd('taglowercase');
  return resultIndex;
}

export default class Search {
  static searchLocationIndex = (locationContent: Array<Object>, searchQuery: SearchQuery): Promise<Array<Object> | []> => new Promise((resolve) => {
    console.time('searchtime');
    const jmespathQuery = constructjmespathQuery(searchQuery);
    let jmespathResults;
    let results;
    let currentDirectoryEntries = transformTagsToLowercase(locationContent);

    if (searchQuery.searchBoxing === 'folder') {
      currentDirectoryEntries = locationContent.filter(entry => entry.path.startsWith(searchQuery.currentDirectory));
    }

    if (jmespathQuery) {
      console.log('jmespath query: ' + jmespathQuery);
      console.time('jmespath');
      jmespathResults = jmespath.search({ index: currentDirectoryEntries || locationContent }, jmespathQuery);
      console.timeEnd('jmespath');
      console.log('jmespath results: ' + jmespathResults.length);
    }

    // if (Pro && Pro.Search.filterIndex) {
    //   results = Pro.Search.filterIndex(results || locationContent, searchQuery);
    // }

    if (searchQuery.textQuery && searchQuery.textQuery.length > 1) {
      console.log('fuse query: ' + searchQuery.textQuery);
      console.time('fuse');
      const fuse = new Fuse(jmespathResults || currentDirectoryEntries || locationContent, fuseOptions);
      results = fuse.search(searchQuery.textQuery);
      console.timeEnd('fuse');
    } else {
      results = jmespathResults;
    }

    if (results) {
      console.log('Results found: ' + results.length);
      if (searchQuery.maxSearchResults && results.length >= searchQuery.maxSearchResults) {
        results = results.slice(0, searchQuery.maxSearchResults);
      }
      console.log('Results send: ' + results.length);
      console.timeEnd('searchtime');
      resolve(results);
      return true;
    }
    results = [];
    console.timeEnd('searchtime');
    resolve(results);
  });
}
<|endoftext|>


<commit_before>/**
 * Created by hapresto on 2/14/17.
 */

app.constant('demos', [
    {
        "status": "active",
        "name": "Cisco Health Report & ONline Information Collector (CHROnIC)",
        "github": "https://github.com/imapex/CHROnIC",
        "youtube": "https://www.youtube.com/embed/VEXby7V8Szs",
        "devs": [
            {
                "name": "Josh Anderson",
                "github": "https://github.com/joshand",
                "twitter": "josh_l_anderson"
            },
            {
                "name": "Chad Peterson",
                "github": "https://github.com/chapeter"
            },
            {
                "name": "Loy Evans",
                "github": "https://github.com/loyevans"
            }
        ],
        "description": "This is an application designed to interact with Cisco Infrastructure components and key software elements, and perform an online analysis of their compliance to set standards.",
        "image": "demoresources/chronic/chronic.png"
    },
    {
        "status": "active",
        "name": "Cisco Kiddie Kounter (C2K-IoX)",
        "github": "https://github.com/imapex/c2k_demo",
        "youtube": "https://www.youtube.com/embed/uacr-M5RM-g",
        "devs": [
            {
                "name": "Ryan Criss",
                "github": "https://github.com/rcriss"
            },
            {
                "name": "Chuck Wood",
                "github": "https://github.com/cw082195"
            },
            {
                "name": "Harish Chawla",
                "github": "https://github.com/"
            }
        ],
        "description": "An application to count kids on a school bus and provide notifications via Spark and/or Tropo/SMS.",
        "image": "demoresources/c2k_iox/c2k-diagram.png"
    },
    {
        "status": "active",
        "name": "Automated Infrastructure Configuration",
        "github": "https://github.com/imapex/aic_app",
        "youtube": "https://www.youtube.com/embed/n-mJIbw-1Uw",
        "devs": [
            {
                "name": "Michael Bonnett Jr",
                "github": "https://github.com/michaelbonnettjr"
            },
            {
                "name": "Rusty Pesselato",
                "github": "https://github.com/opessela"
            },
            {
                "name": "Dan Williams",
                "github": "https://github.com/danwms3"
            }
        ],
        "description": "AIC is an application that monitors for infrastructure changes, such as HBA hardware replacement, and automatically reconfigures the affected systems to account for the change.",
        "image": "demoresources/aic/aic_logical_arch.png"
    },
    {
        "status": "active",
        "name": "All Hands On Deck (AHOD)",
        "github": "https://github.com/imapex/ahod_home",
        "youtube": "https://www.youtube.com/embed/ytXI272kDxg",
        "devs": [
            {
                "name": "Dan Behrens",
                "github": "https://github.com/danbehrens1",
                "twitter": "danielrbehrens"
            },
            {
                "name": "Peter Fincher",
                "github": "https://github.com/pfincher99"
            }
        ],
        "description": "An IOx Application that gathers data from PLC, monitors for specific events, and alerts via Cisco Spark when a monitored event occurs.",
        "image": "demoresources/ahod/AHOD_Overview-Phase1.jpg"
    },
    {
        "status": "active",
        "name": "Cisco Heat Indication Visualization Explorer (CHIVE)",
        "github": "https://github.com/imapex/chive_demo",

        "devs": [
            {
                "name": "Justin Barksdale",
                "github": "https://github.com/3pings",
                "twitter": "3Pings"
            },
            {
                "name": "Clint Mann",
                "github": "https://github.com/clintmann",
                "twitter": "CiscoMann_SE"
            }
        ],
        "description": "Cisco Heat Indication Visualization Explorer (An application to visualize hot spots in a data center on a map and over time using standard API calls on sensors in Cisco Data Center Products.)"
    },
    {
        "status": "active",
        "name": "ACI Migration Wizard",
        "github": "https://github.com/imapex/acimigrate",
        "youtube": "https://www.youtube.com/embed/SzjwplFwkL8",
        "devs": [
            {
                "name": "Kevin Corbin",
                "github": "https://github.com/kecorbin"
            },
            {
                "name": "Chad Peterson",
                "github": "https://github.com/chapeter"
            }
        ],
        "description": "A migration wizard which will migrate VLAN configuration from Legacy Nexus environments to an ACI fabric",
        "image": "demoresources/acimigrate/acimigrate.png"
    },
    {
        "status": "active",
        "name": "Giant Ball of String (GBoS)",
        "github": "https://github.com/imapex/gbos_demo",
        "youtube": "https://www.youtube.com/embed/ySjgylacZDU",
        "devs": [
            {
                "name": "Hank Preston",
                "github": "https://github.com/hpreston",
                "twitter": "hfpreston"
            },
            {
                "name": "Kevin Corbin",
                "github": "https://github.com/kecorbin",
            }
        ],
        "description": "Giant Ball of String (GBoS) is a sample demonstration application for road side attraction management.  It demonstrates the integration of several Cisco technologies (IOx, Cisco Zeus, Spark, and Tropo) with non-Cisco techologies such as cloud platforms and micro-controllers to solve business problems.",
        "image": "demoresources/gbos/gbos_site.jpg",
        "live_demo": [
            {
                "name": "Demo Link (Kiosk)",
                "link": "http://gbos.apps.imapex.io/"
            }
        ]
    },
    {
        "status": "active",
        "name": "Event Driven QoS",
        "github": "https://github.com/imapex/ed-qos",
        "youtube": "https://www.youtube.com/embed/cxmlz4XkV24",
        "devs": [
            {
                "name": "Steve Luzynski",
                "github": "https://github.com/sluzynsk",
                "twitter": "SteveLuzynski"
            },
            {
                "name": "Matt Brainard",
                "github": "https://github.com/mbrainar"
            }
        ],
        "description": "Event Driven QoS is a simple demo application illustrating programmatic control of APIC-EM to change QoS parameters in an attached network. This is driven by an external event. In the initial implementation, the external event source is changes in the weather; other event sources are planned."
    },
    {
        "status": "active",
        "name": "CMX Spark Alert (csalert)",
        "github": "https://github.com/imapex/csalert",
        "youtube": "https://www.youtube.com/embed/sFghP0lqdv4",
        "devs": [
            {
                "name": "Bryan Adams",
                "github": "https://github.com/bmadams0"
            },
            {
                "name": "Paul Lewis",
                "github": "https://github.com/paullewi"
            }
        ],
        "description": "The goal is to create an application that will take location-based event triggers from CMX and output a Spark message to a specific room or user. The CMX input could be device tracking, congestion thresholds, geo-fencing alerts, etc.
 Our use case is a retail store that will detect shopper congestion (by tracking client count on a nearby AP) and send an alert message to the manager or employee in that department so that they can assist the shoppers.
 We would like to keep the app modular to adjust both the CMX inputs and the Spark output.",
        "image": "demoresources/csalert/csalert.png"
    },
    {
        "status": "active",
        "name": "AppRegistry",
        "github": "https://github.com/imapex/appregistry",

        "devs": [
            {
                "name": "Kevin Corbin",
                "github": "https://github.com/kecorbin"
            }
        ],
        "description": "This is a simple frontend for APIC-EM which allows business owners to register applications in a portal with a minimal amount of technical details about the application, and transfer that information to APIC-EM to be consumed in EasyQos policies",
        "image": "demoresources/appregistry/screenshot.png",
        "live_demo": [
            {
                "name": "Demo Link",
                "link": "http://appregistry.apps.imapex.io/"
            }
        ]
    },
    {
        "status": "active",
        "name": "ioxduino",
        "github": "https://github.com/imapex/ioxduino",
        "youtube": "https://www.youtube.com/embed/KFQw7loxLJ0",
        "devs": [
            {
                "name": "Hank Preston",
                "github": "https://github.com/hpreston",
                "twitter": "hfpreston"

            }
        ],
        "description": "IOx Proof of Concept to integrate a Cisco Router with an Arduino Microcontroller.",
        "image": "demoresources/ioxduino/arduino_diagram.jpg"
    },
    {
        "status": "active",
        "name": "MyHero",
        "github": "https://github.com/hpreston/myhero_demo",

        "devs": [
            {
                "name": "Hank Preston",
                "github": "https://github.com/hpreston",
                "twitter": "hfpreston"

            }
        ],
        "description": "MyHero is a very simple sample application developed using a \"Cloud Native\" design method to be used for demonstration and education purposes. It is composed of several microservices, each leveraging standard API interaction. The minimal deployment of MyHero consists of just three services, and when deployed with all features, it can be as many as ten different services.",
        "image": "demoresources/myhero/myhero-demo-i2.png",
        "live_demo": [
            {
                "name": "Demo Link",
                "link": "http://myhero.apps.imapex.io/#/demos"
            }
        ]
    },
    {
        "status": "beta",
        "name": "Gomorrah Automated Distributed Defense (GADD)",
        "github": "https://github.com/imapex/gadd",
        "youtube": "https://www.youtube.com/embed/Nxex1kpwRYA",
        "devs": [
            {
                "name": "Chet Puskarz",
                "github": "https://github.com/cpuskarz"
            },
            {
                "name": "Ryan Shoemaker",
                "github": "https://github.com/rshoemak"
            }
        ],
        "description": "Using Spark and NFVIS along with ACI and NGIPS* The idea of the demo is based on an event/alert in the datacenter, dynamically deploy a FW at offending remote brach. Phase 1 will be simulating an event/alert without an IPS."
    },
    {
        "status": "beta",
        "name": "DC Jeeves",
        "github": "https://github.com/imapex/dcjeeves_demo",
        "youtube": "https://www.youtube.com/embed/3vpalwCzRCw",
        "devs": [
            {
                "name": "Mike Tokarz",
                "github": "https://github.com/myTokarz",
                "twitter": "myTokarz"
            },
            {
                "name": "Russel Wilkinson",
                "github": "https://github.com/virtualrussel"
            }
        ],
        "description": "DC Jeeves is your adaptable datacenter butler. His goal is to make your life easier by providing an interactive interface through tools like Spark & Tropo and turning your requests into actionable functions in your datacenter. Jeeves, provision a server for me!"
    },
    {
        "status": "beta",
        "name": "IOT Notification Agent",
        "github": "https://github.com/imapex/IOTEventNotification",
        "youtube": "https://www.youtube.com/embed/FheCN6B42N4",
        "devs": [
            {
                "name": "Kevin Corbin",
                "github": "https://github.com/kecorbin"
            },
            {
                "name": "Chris Bogdon",
                "github": "https://github.com/chrisbog",
                "twitter": "ChrisBogDog"
            }
        ],
        "description": "This application serves as a generic event notification engine for IOT enabled routers. The engine will react to any external event and perform some type of notification."
    },
    {
        "status": "beta",
        "name": "Spark Trak",
        "github": "https://github.com/imapex/spark-trak",

        "devs": [
            {
                "name": "Blair Hicks",
                "github": "https://github.com/blairhicks",
                "twitter": "tuxdbird"
            },
            {
                "name": "Tim Wittbrod",
                "github": "https://github.com/twittbrod"
            }
        ],
        "description": "Spark Trak is a professional collaboration application leveraging Cisco Spark to provide secure communication.  Intended to be used by health care professionals, laywers, insurance agents, etc - any professional that needs to share information pertaining to a client.  By entering into Spark Room, the professionals are able to discuss and access confidential records. While in the Spark Room, they are able to initiate actions relevant to the client - order procedures, prescribe medication, set appointments.  Upon exiting the Spark room, the collaboration session is archived and can generate a billing record for the client."
    }
]);<commit_msg>Add TAC Bot<commit_after>/**
 * Created by hapresto on 2/14/17.
 */

app.constant('demos', [
    {
        "status": "active",
        "name": "Cisco Health Report & ONline Information Collector (CHROnIC)",
        "github": "https://github.com/imapex/CHROnIC",
        "youtube": "https://www.youtube.com/embed/VEXby7V8Szs",
        "devs": [
            {
                "name": "Josh Anderson",
                "github": "https://github.com/joshand",
                "twitter": "josh_l_anderson"
            },
            {
                "name": "Chad Peterson",
                "github": "https://github.com/chapeter"
            },
            {
                "name": "Loy Evans",
                "github": "https://github.com/loyevans"
            }
        ],
        "description": "This is an application designed to interact with Cisco Infrastructure components and key software elements, and perform an online analysis of their compliance to set standards.",
        "image": "demoresources/chronic/chronic.png"
    },
    {
        "status": "active",
        "name": "Cisco Kiddie Kounter (C2K-IoX)",
        "github": "https://github.com/imapex/c2k_demo",
        "youtube": "https://www.youtube.com/embed/uacr-M5RM-g",
        "devs": [
            {
                "name": "Ryan Criss",
                "github": "https://github.com/rcriss"
            },
            {
                "name": "Chuck Wood",
                "github": "https://github.com/cw082195"
            },
            {
                "name": "Harish Chawla",
                "github": "https://github.com/"
            }
        ],
        "description": "An application to count kids on a school bus and provide notifications via Spark and/or Tropo/SMS.",
        "image": "demoresources/c2k_iox/c2k-diagram.png"
    },
    {
        "status": "active",
        "name": "Automated Infrastructure Configuration",
        "github": "https://github.com/imapex/aic_app",
        "youtube": "https://www.youtube.com/embed/n-mJIbw-1Uw",
        "devs": [
            {
                "name": "Michael Bonnett Jr",
                "github": "https://github.com/michaelbonnettjr"
            },
            {
                "name": "Rusty Pesselato",
                "github": "https://github.com/opessela"
            },
            {
                "name": "Dan Williams",
                "github": "https://github.com/danwms3"
            }
        ],
        "description": "AIC is an application that monitors for infrastructure changes, such as HBA hardware replacement, and automatically reconfigures the affected systems to account for the change.",
        "image": "demoresources/aic/aic_logical_arch.png"
    },
    {
        "status": "active",
        "name": "All Hands On Deck (AHOD)",
        "github": "https://github.com/imapex/ahod_home",
        "youtube": "https://www.youtube.com/embed/ytXI272kDxg",
        "devs": [
            {
                "name": "Dan Behrens",
                "github": "https://github.com/danbehrens1",
                "twitter": "danielrbehrens"
            },
            {
                "name": "Peter Fincher",
                "github": "https://github.com/pfincher99"
            }
        ],
        "description": "An IOx Application that gathers data from PLC, monitors for specific events, and alerts via Cisco Spark when a monitored event occurs.",
        "image": "demoresources/ahod/AHOD_Overview-Phase1.jpg"
    },
    {
        "status": "active",
        "name": "Cisco Heat Indication Visualization Explorer (CHIVE)",
        "github": "https://github.com/imapex/chive_demo",

        "devs": [
            {
                "name": "Justin Barksdale",
                "github": "https://github.com/3pings",
                "twitter": "3Pings"
            },
            {
                "name": "Clint Mann",
                "github": "https://github.com/clintmann",
                "twitter": "CiscoMann_SE"
            }
        ],
        "description": "Cisco Heat Indication Visualization Explorer (An application to visualize hot spots in a data center on a map and over time using standard API calls on sensors in Cisco Data Center Products.)"
    },
    {
        "status": "active",
        "name": "ACI Migration Wizard",
        "github": "https://github.com/imapex/acimigrate",
        "youtube": "https://www.youtube.com/embed/SzjwplFwkL8",
        "devs": [
            {
                "name": "Kevin Corbin",
                "github": "https://github.com/kecorbin"
            },
            {
                "name": "Chad Peterson",
                "github": "https://github.com/chapeter"
            }
        ],
        "description": "A migration wizard which will migrate VLAN configuration from Legacy Nexus environments to an ACI fabric",
        "image": "demoresources/acimigrate/acimigrate.png"
    },
    {
        "status": "active",
        "name": "Giant Ball of String (GBoS)",
        "github": "https://github.com/imapex/gbos_demo",
        "youtube": "https://www.youtube.com/embed/ySjgylacZDU",
        "devs": [
            {
                "name": "Hank Preston",
                "github": "https://github.com/hpreston",
                "twitter": "hfpreston"
            },
            {
                "name": "Kevin Corbin",
                "github": "https://github.com/kecorbin",
            }
        ],
        "description": "Giant Ball of String (GBoS) is a sample demonstration application for road side attraction management.  It demonstrates the integration of several Cisco technologies (IOx, Cisco Zeus, Spark, and Tropo) with non-Cisco techologies such as cloud platforms and micro-controllers to solve business problems.",
        "image": "demoresources/gbos/gbos_site.jpg",
        "live_demo": [
            {
                "name": "Demo Link (Kiosk)",
                "link": "http://gbos.apps.imapex.io/"
            }
        ]
    },
    {
        "status": "active",
        "name": "Event Driven QoS",
        "github": "https://github.com/imapex/ed-qos",
        "youtube": "https://www.youtube.com/embed/cxmlz4XkV24",
        "devs": [
            {
                "name": "Steve Luzynski",
                "github": "https://github.com/sluzynsk",
                "twitter": "SteveLuzynski"
            },
            {
                "name": "Matt Brainard",
                "github": "https://github.com/mbrainar"
            }
        ],
        "description": "Event Driven QoS is a simple demo application illustrating programmatic control of APIC-EM to change QoS parameters in an attached network. This is driven by an external event. In the initial implementation, the external event source is changes in the weather; other event sources are planned."
    },
    {
        "status": "active",
        "name": "CMX Spark Alert (csalert)",
        "github": "https://github.com/imapex/csalert",
        "youtube": "https://www.youtube.com/embed/sFghP0lqdv4",
        "devs": [
            {
                "name": "Bryan Adams",
                "github": "https://github.com/bmadams0"
            },
            {
                "name": "Paul Lewis",
                "github": "https://github.com/paullewi"
            }
        ],
        "description": "The goal is to create an application that will take location-based event triggers from CMX and output a Spark message to a specific room or user. The CMX input could be device tracking, congestion thresholds, geo-fencing alerts, etc.
 Our use case is a retail store that will detect shopper congestion (by tracking client count on a nearby AP) and send an alert message to the manager or employee in that department so that they can assist the shoppers.
 We would like to keep the app modular to adjust both the CMX inputs and the Spark output.",
        "image": "demoresources/csalert/csalert.png"
    },
    {
        "status": "active",
        "name": "AppRegistry",
        "github": "https://github.com/imapex/appregistry",

        "devs": [
            {
                "name": "Kevin Corbin",
                "github": "https://github.com/kecorbin"
            }
        ],
        "description": "This is a simple frontend for APIC-EM which allows business owners to register applications in a portal with a minimal amount of technical details about the application, and transfer that information to APIC-EM to be consumed in EasyQos policies",
        "image": "demoresources/appregistry/screenshot.png",
        "live_demo": [
            {
                "name": "Demo Link",
                "link": "http://appregistry.apps.imapex.io/"
            }
        ]
    },
    {
        "status": "active",
        "name": "ioxduino",
        "github": "https://github.com/imapex/ioxduino",
        "youtube": "https://www.youtube.com/embed/KFQw7loxLJ0",
        "devs": [
            {
                "name": "Hank Preston",
                "github": "https://github.com/hpreston",
                "twitter": "hfpreston"

            }
        ],
        "description": "IOx Proof of Concept to integrate a Cisco Router with an Arduino Microcontroller.",
        "image": "demoresources/ioxduino/arduino_diagram.jpg"
    },
    {
        "status": "active",
        "name": "MyHero",
        "github": "https://github.com/hpreston/myhero_demo",

        "devs": [
            {
                "name": "Hank Preston",
                "github": "https://github.com/hpreston",
                "twitter": "hfpreston"

            }
        ],
        "description": "MyHero is a very simple sample application developed using a \"Cloud Native\" design method to be used for demonstration and education purposes. It is composed of several microservices, each leveraging standard API interaction. The minimal deployment of MyHero consists of just three services, and when deployed with all features, it can be as many as ten different services.",
        "image": "demoresources/myhero/myhero-demo-i2.png",
        "live_demo": [
            {
                "name": "Demo Link",
                "link": "http://myhero.apps.imapex.io/#/demos"
            }
        ]
    },
    {
        "status": "beta",
        "name": "Gomorrah Automated Distributed Defense (GADD)",
        "github": "https://github.com/imapex/gadd",
        "youtube": "https://www.youtube.com/embed/Nxex1kpwRYA",
        "devs": [
            {
                "name": "Chet Puskarz",
                "github": "https://github.com/cpuskarz"
            },
            {
                "name": "Ryan Shoemaker",
                "github": "https://github.com/rshoemak"
            }
        ],
        "description": "Using Spark and NFVIS along with ACI and NGIPS* The idea of the demo is based on an event/alert in the datacenter, dynamically deploy a FW at offending remote brach. Phase 1 will be simulating an event/alert without an IPS."
    },
    {
        "status": "beta",
        "name": "DC Jeeves",
        "github": "https://github.com/imapex/dcjeeves_demo",
        "youtube": "https://www.youtube.com/embed/3vpalwCzRCw",
        "devs": [
            {
                "name": "Mike Tokarz",
                "github": "https://github.com/myTokarz",
                "twitter": "myTokarz"
            },
            {
                "name": "Russel Wilkinson",
                "github": "https://github.com/virtualrussel"
            }
        ],
        "description": "DC Jeeves is your adaptable datacenter butler. His goal is to make your life easier by providing an interactive interface through tools like Spark & Tropo and turning your requests into actionable functions in your datacenter. Jeeves, provision a server for me!"
    },
    {
        "status": "beta",
        "name": "IOT Notification Agent",
        "github": "https://github.com/imapex/IOTEventNotification",
        "youtube": "https://www.youtube.com/embed/FheCN6B42N4",
        "devs": [
            {
                "name": "Kevin Corbin",
                "github": "https://github.com/kecorbin"
            },
            {
                "name": "Chris Bogdon",
                "github": "https://github.com/chrisbog",
                "twitter": "ChrisBogDog"
            }
        ],
        "description": "This application serves as a generic event notification engine for IOT enabled routers. The engine will react to any external event and perform some type of notification."
    },
    {
        "status": "beta",
        "name": "Spark Trak",
        "github": "https://github.com/imapex/spark-trak",

        "devs": [
            {
                "name": "Blair Hicks",
                "github": "https://github.com/blairhicks",
                "twitter": "tuxdbird"
            },
            {
                "name": "Tim Wittbrod",
                "github": "https://github.com/twittbrod"
            }
        ],
        "description": "Spark Trak is a professional collaboration application leveraging Cisco Spark to provide secure communication.  Intended to be used by health care professionals, laywers, insurance agents, etc - any professional that needs to share information pertaining to a client.  By entering into Spark Room, the professionals are able to discuss and access confidential records. While in the Spark Room, they are able to initiate actions relevant to the client - order procedures, prescribe medication, set appointments.  Upon exiting the Spark room, the collaboration session is archived and can generate a billing record for the client."
    },
    {
        "status": "active",
        "name": "TAC Bot",
        "github": "https://github.com/imapex/tac-bot",
        "youtube": "https://www.youtube.com/embed/61D02aYOP1U",
        "devs": [
            {
                "name": "Matthew Brainard",
                "github": "https://github.com/mbrainar",
                "twitter": "mbrainar"
            },
            {
                "name": "Steven Luzynski",
                "github": "https://github.com/sluzynsk",
                "twitter": "SteveLuzynski"
            }
        ],
        "description": "TAC Bot is a Spark bot designed to help facilitate communications between Cisco SEs, TAC engineers, escalation teams, and potentially customers. In one place, Cisco SEs can get status of TAC case from a CASE API, including but not limited to case owner, description, when the case was last updated, etc."
    }
]);
<|endoftext|>


<commit_before>'use strict';

angular.module('myApp.sliders', ['ngRoute'])

  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/sliders', {
      templateUrl: 'sliders/sliders.html',
      controller: 'SlidersController'
    });
  }])

  /**
   * The sliders controller handles the sliders for the /sliders page and allows users full control over the ranges
   * of the perceptual features.
   */
  .controller('SlidersController', ['$scope', 'ResourcesService', 'SongRequestService', 'angularPlayer', 'SlidersService','$timeout',
    function ($scope, ResourcesService, SongRequestService, angularPlayer, SlidersService, $timeout) {
      const ERROR_DURATION = 3000;
      var activeErrors = 0;

      /**
       * Shows the "No songs found"-error
       * @param duration The duration to show the error for
       */
      var showNoSongsFoundError = function (duration) {
        $scope.showError = true;
        activeErrors++;
        var thisError = activeErrors;
        $timeout(function () {
          if (thisError === activeErrors) { // No new errors have been added during the timeout
            $scope.showError = false;
            activeErrors = 0;
          }
        }, duration);
      };

      /**
       * Callback function which gets called when songs are to be added
       * @param res The added songs
       */
      var addedSongs = function (res) {
        if (res.length === 0) {
          showNoSongsFoundError(ERROR_DURATION);
        }
      };

      /**
       * Sets the location of the error
       * @param sliderId The id of the slider used
       */
      var setErrorLocation = function(sliderId){
        var slider = $('.'+sliderId);
        $scope.errorLeft = slider.offset().left+slider.width()/6;
        $scope.errorTop = slider.offset().top+35;
      };

      /**
       * Sends a request to play songs matching the features selected in the sliders
       */
      $scope.sendRequestForClosest = function (sliderId) {
        setErrorLocation(sliderId);
        SlidersService.saveSliders($scope.featureList);
        SongRequestService.playMatchingSongs($scope.featureList,addedSongs,"Closest");
        SlidersService.setLastRequestNumber(SongRequestService.getLastRequestNumber());
      };

      /**
       * Resets all the sliders to the original settings
       */
      $scope.resetSliders = function(){
        for(var i = 0; i < $scope.featureList.length; i++){
          $scope.featureList[i].minValue = 0;
          $scope.featureList[i].maxValue = 100;
        }
      };

      /**
       * Loads existing values
       */
      var loadValues = function () {
        $scope.featureList = SlidersService.getSavedValues().features;
        // Check if another request has been done on another page, if so, reset the sliders
        if(SlidersService.getLastRequestNumber() !== SongRequestService.getLastRequestNumber()){
          $scope.resetSliders();
        }
      };

      /**
       * Sets that the page has fully loaded after 200 ms
       */
      var setPageLoaded = function() {
        $timeout(function () {
          $scope.pageLoaded = true;
        }, 200);
      };

      /* Controller body starts here */

      $scope.featureList = [];

      if(SlidersService.hasTransferred()){ // Check if user has copied values to the sliders page
        ResourcesService.Features.query().$promise.then(function (data) {
          $scope.featureList = SlidersService.getTransferredValues();
          for (var i = 0; i < data.length; i++){
            $scope.featureList[i].low = data[i].low;
            $scope.featureList[i].high = data[i].high;
          }
          SlidersService.clearTransferredValues();
          SlidersService.saveSliders($scope.featureList);
          setPageLoaded();
        }, function (err) {
          throw "No features were returned by query: " + err;
        });
      }
      else if (!SlidersService.getSavedValues().features) { // Import saved values if possible
        ResourcesService.Features.query().$promise.then(function (data) {
          $scope.featureList = data;
          for (var i = 0; i < $scope.featureList.length; i++) {
            $scope.featureList[i].minValue = 0;
            $scope.featureList[i].maxValue = 100;
          }
          setPageLoaded();
        }, function (err) {
          throw "No features were returned by query: " + err;
        });
      }
      else { // No transferred or saved values exist, build the list ourselves
        loadValues();
        setPageLoaded();
      }

    }]);<commit_msg>Resetting sliders now sends a new request for music rather than keeps playing.<commit_after>'use strict';

angular.module('myApp.sliders', ['ngRoute'])

  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/sliders', {
      templateUrl: 'sliders/sliders.html',
      controller: 'SlidersController'
    });
  }])

  /**
   * The sliders controller handles the sliders for the /sliders page and allows users full control over the ranges
   * of the perceptual features.
   */
  .controller('SlidersController', ['$scope', 'ResourcesService', 'SongRequestService', 'angularPlayer', 'SlidersService','$timeout',
    function ($scope, ResourcesService, SongRequestService, angularPlayer, SlidersService, $timeout) {
      const ERROR_DURATION = 3000;
      var activeErrors = 0;

      /**
       * Shows the "No songs found"-error
       * @param duration The duration to show the error for
       */
      var showNoSongsFoundError = function (duration) {
        $scope.showError = true;
        activeErrors++;
        var thisError = activeErrors;
        $timeout(function () {
          if (thisError === activeErrors) { // No new errors have been added during the timeout
            $scope.showError = false;
            activeErrors = 0;
          }
        }, duration);
      };

      /**
       * Callback function which gets called when songs are to be added
       * @param res The added songs
       */
      var addedSongs = function (res) {
        if (res.length === 0) {
          showNoSongsFoundError(ERROR_DURATION);
        }
      };

      /**
       * Sets the location of the error
       * @param sliderId The id of the slider used
       */
      var setErrorLocation = function(sliderId){
        var slider = $('.'+sliderId);
        $scope.errorLeft = slider.offset().left+slider.width()/6;
        $scope.errorTop = slider.offset().top+35;
      };

      /**
       * Sends a request to play songs matching the features selected in the sliders
       */
      $scope.sendRequestForClosest = function (sliderId) {
        setErrorLocation(sliderId);
        SlidersService.saveSliders($scope.featureList);
        SongRequestService.playMatchingSongs($scope.featureList,addedSongs,"Closest");
        SlidersService.setLastRequestNumber(SongRequestService.getLastRequestNumber());
      };

      /**
       * Resets all the sliders to the original settings
       */
      $scope.resetSliders = function(){
        for(var i = 0; i < $scope.featureList.length; i++){
          $scope.featureList[i].minValue = 0;
          $scope.featureList[i].maxValue = 100;
        }
        SlidersService.saveSliders($scope.featureList);
        SongRequestService.playMatchingSongs($scope.featureList,addedSongs,"Closest");
        SlidersService.setLastRequestNumber(SongRequestService.getLastRequestNumber());
      };

      /**
       * Loads existing values
       */
      var loadValues = function () {
        $scope.featureList = SlidersService.getSavedValues().features;
        // Check if another request has been done on another page, if so, reset the sliders
        if(SlidersService.getLastRequestNumber() !== SongRequestService.getLastRequestNumber()){
          $scope.resetSliders();
        }
      };

      /**
       * Sets that the page has fully loaded after 200 ms
       */
      var setPageLoaded = function() {
        $timeout(function () {
          $scope.pageLoaded = true;
        }, 200);
      };

      /* Controller body starts here */

      $scope.featureList = [];

      if(SlidersService.hasTransferred()){ // Check if user has copied values to the sliders page
        ResourcesService.Features.query().$promise.then(function (data) {
          $scope.featureList = SlidersService.getTransferredValues();
          for (var i = 0; i < data.length; i++){
            $scope.featureList[i].low = data[i].low;
            $scope.featureList[i].high = data[i].high;
          }
          SlidersService.clearTransferredValues();
          SlidersService.saveSliders($scope.featureList);
          setPageLoaded();
        }, function (err) {
          throw "No features were returned by query: " + err;
        });
      }
      else if (!SlidersService.getSavedValues().features) { // Import saved values if possible
        ResourcesService.Features.query().$promise.then(function (data) {
          $scope.featureList = data;
          for (var i = 0; i < $scope.featureList.length; i++) {
            $scope.featureList[i].minValue = 0;
            $scope.featureList[i].maxValue = 100;
          }
          setPageLoaded();
        }, function (err) {
          throw "No features were returned by query: " + err;
        });
      }
      else { // No transferred or saved values exist, build the list ourselves
        loadValues();
        setPageLoaded();
      }

    }]);<|endoftext|>


<commit_before> function main() {
         var map = new L.Map('map', {
             zoomControl: false,
             center: [44.908, -75.83],
             zoom: 15
         });
         L.tileLayer(
             'http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}', {
                 attribution: 'Tiles © Esri — Esri, DeLorme, NAVTEQ',
                 maxZoom: 16
             }).addTo(map);
         // load 10k course route
         $.getJSON("10k.geojson", function(data) {
             // add GeoJSON layer to the map once the file is loaded
             L.geoJson(data).addTo(map);
         });
         // Define markers
         var startIcon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: 'flag-checkered',
             iconColor: 'white',
             markerColor: 'cadetblue'
         });
         var markerIcon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 //html: i+1
         });
         // load 10k course route features
         $.getJSON("features.geojson", function(data) {
             //add GeoJSON layer to the map once the file is loaded
             L.geoJson(data, {
                 pointToLayer: function(feature, latlng) {
                     if (feature.properties.cng_Meters ==
                         "0.0") {
                         var marker = L.marker(latlng, {
                             icon: startIcon
                         });
                     } else {
                         var marker = L.marker(latlng, {
                             icon: markerIcon
                         });
                     };
                     return marker;
                     //return new L.marker(latlng, {
                     //icon: startIcon
                     //});
                 },
                 //onEachFeature: function(feature, layer) {
                 //layer.bindPopup(feature.properties.cng_Meters);
                 //}
             }).addTo(map);
         });
     }
     // you could use $(window).load(main);
 window.onload = main;<commit_msg>update markers<commit_after> function main() {
         var map = new L.Map('map', {
             zoomControl: false,
             center: [44.908, -75.83],
             zoom: 15
         });
         L.tileLayer(
             'http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}', {
                 attribution: 'Tiles © Esri — Esri, DeLorme, NAVTEQ',
                 maxZoom: 16
             }).addTo(map);
         // load 10k course route
         $.getJSON("10k.geojson", function(data) {
             // add GeoJSON layer to the map once the file is loaded
             L.geoJson(data).addTo(map);
         });
         // Define markers
         var startIcon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: 'flag-checkered',
             iconColor: 'white',
             markerColor: 'cadetblue'
         });
         var km1Icon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "1km"
         });
		 var km2Icon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "2km"
         });
		 var km3Icon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "3km"
         });
		 var km4Icon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "4km"
         });
		 var km5Icon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "5km"
         });
		 var km6Icon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "6km"
         });
		 var km7Icon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "7km"
         });
		 var km8Icon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "8km"
         });
		 var km9Icon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "9km"
         });
		 var defaultIcon = L.AwesomeMarkers.icon({
             prefix: 'fa',
             icon: '1',
             iconColor: 'white',
             markerColor: 'cadetblue',
			 html: "1km"
         // load 10k course route features
         $.getJSON("features.geojson", function(data) {
             //add GeoJSON layer to the map once the file is loaded
             L.geoJson(data, {
                 pointToLayer: function(feature, latlng) {
                     if (feature.properties.cng_Meters ==
                         "0.0") {
                         var marker = L.marker(latlng, {
                             icon: startIcon
                         });
					if (feature.properties.cng_Meters ==
                         "1000.0") {
                         var marker = L.marker(latlng, {
                             icon: km1Icon
                         });
					if (feature.properties.cng_Meters ==
                         "2000.0") {
                         var marker = L.marker(latlng, {
                             icon: km2Icon
                         });
					if (feature.properties.cng_Meters ==
                         "3000.0") {
                         var marker = L.marker(latlng, {
                             icon: km3Icon
                         });
					if (feature.properties.cng_Meters ==
                         "4000.0") {
                         var marker = L.marker(latlng, {
                             icon: km4Icon
                         });
					if (feature.properties.cng_Meters ==
                         "5000.0") {
                         var marker = L.marker(latlng, {
                             icon: km5Icon
                         });
					if (feature.properties.cng_Meters ==
                         "6000.0") {
                         var marker = L.marker(latlng, {
                             icon: km6Icon
                         });
					if (feature.properties.cng_Meters ==
                         "7000.0") {
                         var marker = L.marker(latlng, {
                             icon: km7Icon
                         });
					if (feature.properties.cng_Meters ==
                         "8000.0") {
                         var marker = L.marker(latlng, {
                             icon: km8Icon
                         });
					if (feature.properties.cng_Meters ==
                         "9000.0") {
                         var marker = L.marker(latlng, {
                             icon: km9Icon
                         });
                     } else {
                         var marker = L.marker(latlng, {
                             icon: defaultIcon
                         });
                     };
                     return marker;
                     //return new L.marker(latlng, {
                     //icon: startIcon
                     //});
                 },
                 //onEachFeature: function(feature, layer) {
                 //layer.bindPopup(feature.properties.cng_Meters);
                 //}
             }).addTo(map);
         });
     }
     // you could use $(window).load(main);
 window.onload = main;<|endoftext|>


<commit_before>const webpack = require('webpack');

const webpackConfig = require('./conf.common');
const moduleConfigDev = require('./module.dev');

module.exports = Object.assign({}, webpackConfig, {
    devtool: 'source-map',
    entry: [
        `webpack-dev-server/client?http://0.0.0.0:${process.env.PORT_WDS}`,
        'webpack/hot/only-dev-server'
    ].concat(webpackConfig.entry),
    plugins: webpackConfig.plugins.concat([
        new webpack.HotModuleReplacementPlugin()
    ]),
    module: moduleConfigDev,
    devServer: {
        stats: {
            colors: true,
            modules: false,
            chunks: false,
            reasons: true
        },
        hot: true,
        quiet: false,
        noInfo: false,
        publicPath: '/',
        port: process.env.PORT_WDS,
        proxy: {
            '/': {
                target: `http://localhost:${process.env.PORT}`,
                secure: false
            }
        }
    }
});

<commit_msg>Insert development node env into webpack<commit_after>const webpack = require('webpack');

const webpackConfig = require('./conf.common');
const moduleConfigDev = require('./module.dev');

module.exports = Object.assign({}, webpackConfig, {
    devtool: 'source-map',
    entry: [
        `webpack-dev-server/client?http://0.0.0.0:${process.env.PORT_WDS}`,
        'webpack/hot/only-dev-server'
    ].concat(webpackConfig.entry),
    plugins: webpackConfig.plugins.concat([
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify('development')
            }
        }),
        new webpack.HotModuleReplacementPlugin()
    ]),
    module: moduleConfigDev,
    devServer: {
        stats: {
            colors: true,
            modules: false,
            chunks: false,
            reasons: true
        },
        hot: true,
        quiet: false,
        noInfo: false,
        publicPath: '/',
        port: process.env.PORT_WDS,
        proxy: {
            '/': {
                target: `http://localhost:${process.env.PORT}`,
                secure: false
            }
        }
    }
});

<|endoftext|>


<commit_before>$('div#main-menu a').each(function () {

    let cur_path = window.location.pathname.split('/')[1],
        a_path = $(this)[0].pathname.split('/')[1];

    if (cur_path === a_path) {
        $(this).addClass('active').removeAttr('href');
        return false;
    }
});

$('div#main-menu .dropdown').removeClass('simple').dropdown();

<commit_msg>Don't remove href attr on active<commit_after>$('div#main-menu a').each(function () {

    let cur_path = window.location.pathname.split('/')[1],
        a_path = $(this)[0].pathname.split('/')[1];

    if (cur_path === a_path) {
        $(this).addClass('active');
        return false;
    }
});

$('div#main-menu .dropdown').removeClass('simple').dropdown();

<|endoftext|>


<commit_before>//
// shy-navbar.js - Add “shyness” into your navigation bar.
// https://github.com/lqez/shy-navbar
//
// usage : $("#navbar").shy();
//
(function ($) {
    $.fn.shy = function(options) {
        // Change these variables to suit your favor.
        var settings = $.extend({
            thresholdUp: 10,
            thresholdDown: 80,
            animationDelay: 250,
            animationDur: 150
        }, options);

        var $this = $(this);
        $this.css({
            'position': 'absolute',
                 'top': '0',
             'z-index': '1'
        });
        var lastScrollTop = 0;

        $(window).scroll(function () {
            var position = $this.css('position');
            var height = $this.height();
            var scrollTop = $(this).scrollTop()

            if( scrollTop <= 0 ) {
                $this.clearQueue().css({ top: 0, position: 'absolute' });
            } else {
                if( scrollTop < lastScrollTop ) {
                    if( position != 'fixed' && ( scrollTop + settings.thresholdDown < lastScrollTop ) ) {
                        $this.clearQueue()
                            .css({ top: '-'+height+'px', position: 'fixed' })
                            .delay(settings.animationDelay).animate({ top: 0 }, settings.animationDur);
                    }
                } else if( position == 'fixed' && ( scrollTop - settings.thresholdUp > lastScrollTop ) ) {
                    $this.clearQueue()
                        .delay(settings.animationDelay).animate({ top: '-'+height+'px' }, settings.animationDur, function() {
                            $(this).clearQueue().css({ top: 0, position: 'absolute' });
                        });
                }
            }
            lastScrollTop = scrollTop;
        });
    }
}(jQuery));
<commit_msg>Add $.fn.brave() function to remove the shy effect.<commit_after>//
// shy-navbar.js - Add “shyness” into your navigation bar.
// https://github.com/lqez/shy-navbar
//
// usage : $("#navbar").shy();
//
(function ($) {
    $.fn.shy = function(options) {
        if (options == 'off' || options == 'brave') {
            this.brave();
            return;
        }
        // Change these variables to suit your favor.
        var settings = $.extend({
            thresholdUp: 10,
            thresholdDown: 80,
            animationDelay: 250,
            animationDur: 150
        }, options);

        var $this = this;
        var lastScrollTop = 0;
        var shy = {
            previousStyles: {
                'position': this.css('position'),
                     'top': this.css('top'),
                 'z-index': this.css('z-index')
            },
            handler: function () {
                var position = $this.css('position');
                var height = $this.height();
                var scrollTop = $(this).scrollTop()

                if( scrollTop <= 0 ) {
                    $this.clearQueue().css({ top: 0, position: 'absolute' });
                } else {
                    if( scrollTop < lastScrollTop ) {
                        if( position != 'fixed' && ( scrollTop + settings.thresholdDown < lastScrollTop ) ) {
                            $this.clearQueue()
                                .css({ top: '-'+height+'px', position: 'fixed' })
                                .delay(settings.animationDelay).animate({ top: 0 }, settings.animationDur);
                        }
                    } else if( position == 'fixed' && ( scrollTop - settings.thresholdUp > lastScrollTop ) ) {
                        $this.clearQueue()
                            .delay(settings.animationDelay).animate({ top: '-'+height+'px' }, settings.animationDur, function() {
                                $(this).clearQueue().css({ top: 0, position: 'absolute' });
                            });
                    }
                }
                lastScrollTop = scrollTop;
            }
        };

        this.data('shy', shy);
        this.css({
            'position': 'absolute',
                 'top': '0',
             'z-index': '1'
        });
        $(window).on('scroll', shy.handler);
    };

    $.fn.brave = function() {
        var shy = this.data('shy');

        if (shy && typeof shy.handler == 'function') {
            $(window).off('scroll', shy.handler);
            this.clearQueue();
            for (var key in shy.previousStyles) {
                this.css(key, shy.previousStyles[key]);
            }
        }
    };
}(jQuery));
<|endoftext|>


<commit_before>(function (angular) {
	'use strict';

	angular.module('scroll-spy', [])
		.directive('scrollSpy', scrollSpyDirective)
		.directive('scrollSpyArea', scrollSpyAreaDirective)
		.directive('spyItem', spyItemDirective)
		.directive('spyGroup', spyGroupDirective);

	scrollSpyController.$inject = ['$element', '$timeout', '$document', '$window', '$anchorScroll'];

	function scrollSpyController ($element, $timeout, $document, $window, $anchorScroll) {
		var self = this;

		// div elements associated by target id
		this.groups = {};
		// anchor elements associated by target id
		this.anchors = {};

		var scrollableElement = null,
			defaultOffset = 10,
			offsets = [],
			targets = [],
			activeTarget = null,
			initScrollHeight = 0;

		var mapValues = function (obj) {
			return Object.keys(obj)
				.map(function (key) {
					return obj[key];
				});
		};

		var getScrollHeight = function () {
			return scrollableElement.prop('scrollHeight') || Math.max($document[0].body.scrollHeight, $document[0].scrollHeight);
		};

		var refresh = function () {

			offsets = [];
			targets = [];
			activeTarget = null;
			initScrollHeight = getScrollHeight();

			Object.keys(self.groups)
				.map(function (groupTarget) {
					var groupElement = self.groups[groupTarget];
					if (groupElement.css('visibility') !== 'hidden') {
						return {
							offset: groupElement.prop('offsetTop'),
							target: groupTarget
						}
					}
				})
				.sort(function (a, b) {
					return a.offset - b.offset
				})
				.forEach(function (mappedObj) {
					offsets.push(mappedObj.offset);
					targets.push(mappedObj.target);
				});
		};

		var activateItem = function (target) {
			activeTarget = target;
			clear();

			self.anchors[target].parent().addClass('active');

			self.groups[target].addClass('active');
		};

		var clear = function () {
			mapValues(self.anchors)
				.forEach(function(anchor) {
					anchor.parent().removeClass('active');
				});

			mapValues(self.groups)
				.forEach(function(group) {
					group.removeClass('active');
				});
		};

		var scrollTopWithinGroup = function (scrollTop, groupIndex) {
			return activeTarget !== targets[groupIndex]
				&& scrollTop >= offsets[groupIndex] - defaultOffset
				&& (offsets[groupIndex + 1] === undefined || scrollTop < offsets[groupIndex + 1] - defaultOffset);
		};

		var scrollTopOnScreen = function (scrollTop, groupIndex) {
			return scrollTop <= offsets[groupIndex] - defaultOffset
				&& offsets[groupIndex] < scrollTop + scrollableElement.prop('offsetHeight') - defaultOffset
		};

		this.update = function () {
			var scrollTop = scrollableElement.prop('scrollTop') + defaultOffset;
			var scrollHeight = getScrollHeight();
			var maxScroll = defaultOffset + scrollHeight - scrollableElement.prop('offsetHeight');

			if (scrollHeight !== initScrollHeight) {
				refresh();
			}

			if (scrollTop >= maxScroll) {
				var item = scrollTop === defaultOffset ? targets[0] : targets[targets.length - 1];
				if (activeTarget !== item) {
					activateItem(item);
				}
				return;
			}

			if (activeTarget && scrollTop < offsets[0]) {
				activeTarget = null;
				clear();
				return;
			}

			for (var i = 0; i < offsets.length; i++) {
				if (scrollTopWithinGroup(scrollTop, i)) {
					activateItem(targets[i]);
					return;
				}
			}
		};

		this.activate = function (element, offset) {
			scrollableElement = element;
			defaultOffset = offset;

			element.bind('scroll', self.update);
			var window = angular.element($window);
			window.bind('resize', function () {
				$timeout(self.update, 100);
			});

			refresh();
			self.update();
		};

		this.deactivate = function (element) {
			element.unbind('scroll');
		};

		this.activateItemOnClick = function (target) {
			var scrollTop = scrollableElement.prop('scrollTop') + defaultOffset;
			var targetIndex = targets.indexOf(target);
			if (scrollTopOnScreen(scrollTop, targetIndex)) {
				activateItem(target);
			} else {
				$anchorScroll(target);
				$timeout(function () {
					if (target !== activeTarget) {
						activateItem(target);
					}
				}, 0);
			}
		};

		this.invalidate = function() {
			$element.css('position', 'static');
			$timeout(function () {
				$element.css('position', 'relative');
			}, 0);
		};
	}

	function scrollSpyDirective () {
		return {
			restrict: 'A',
			controller: scrollSpyController,
			link: function (scope, elem, attrs, ctrl) {	}
		};
	}

	function scrollSpyAreaDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				// ToDo: find solution to get calculated overflowY without jQuery
				var $elem = $(elem);

				var offset = parseInt(attrs.spyOffset);
				scope.$watch(function () {
					return $elem.css('overflow-y');
				}, function (value) {
					if (value !== 'hidden') {
						ctrl.activate(elem, offset);
					} else {
						ctrl.deactivate(elem);
						ctrl.invalidate();
					}
				});

				scope.$watch(function () {
					return elem[0].offsetHeight;
				}, function (value) {
					if ($elem.css('overflow-y') !== 'hidden') {
						ctrl.update();
					}
				});
			}
		}
	};

	function spyItemDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				ctrl.anchors[attrs.target] = elem;
				elem.bind('click', function () {
					ctrl.activateItemOnClick(attrs.target);
				});
			}
		}
	}

	function spyGroupDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				ctrl.groups[attrs.id] = elem;
			}
		}
	}
})(angular);
<commit_msg>Watching group on screen fixed<commit_after>(function (angular) {
	'use strict';

	angular.module('scroll-spy', [])
		.directive('scrollSpy', scrollSpyDirective)
		.directive('scrollSpyArea', scrollSpyAreaDirective)
		.directive('spyItem', spyItemDirective)
		.directive('spyGroup', spyGroupDirective);

	scrollSpyController.$inject = ['$element', '$timeout', '$document', '$window', '$anchorScroll'];

	function scrollSpyController ($element, $timeout, $document, $window, $anchorScroll) {
		var self = this;

		// div elements associated by target id
		this.groups = {};
		// anchor elements associated by target id
		this.anchors = {};

		var scrollableElement = null,
			defaultOffset = 10,
			offsets = [],
			targets = [],
			activeTarget = null,
			initScrollHeight = 0;

		var mapValues = function (obj) {
			return Object.keys(obj)
				.map(function (key) {
					return obj[key];
				});
		};

		var getScrollHeight = function () {
			return scrollableElement.prop('scrollHeight') || Math.max($document[0].body.scrollHeight, $document[0].scrollHeight);
		};

		var refresh = function () {

			offsets = [];
			targets = [];
			activeTarget = null;
			initScrollHeight = getScrollHeight();

			Object.keys(self.groups)
				.map(function (groupTarget) {
					var groupElement = self.groups[groupTarget];
					if (groupElement.css('visibility') !== 'hidden') {
						return {
							offset: groupElement.prop('offsetTop'),
							target: groupTarget
						}
					}
				})
				.sort(function (a, b) {
					return a.offset - b.offset
				})
				.forEach(function (mappedObj) {
					offsets.push(mappedObj.offset);
					targets.push(mappedObj.target);
				});
		};

		var activateItem = function (target) {
			activeTarget = target;
			clear();

			self.anchors[target].parent().addClass('active');

			self.groups[target].addClass('active');
		};

		var clear = function () {
			mapValues(self.anchors)
				.forEach(function(anchor) {
					anchor.parent().removeClass('active');
				});

			mapValues(self.groups)
				.forEach(function(group) {
					group.removeClass('active');
				});
		};

		var scrollTopWithinGroup = function (scrollTop, groupIndex) {
			return activeTarget !== targets[groupIndex]
				&& scrollTop >= offsets[groupIndex] - defaultOffset
				&& (offsets[groupIndex + 1] === undefined || scrollTop < offsets[groupIndex + 1] - defaultOffset);
		};

		var scrollTopOnScreen = function (scrollTop, groupIndex) {
			return scrollTop <= offsets[groupIndex] - defaultOffset
				&& offsets[groupIndex] < scrollTop + scrollableElement.prop('offsetHeight')
		};

		this.update = function () {
			var scrollTop = scrollableElement.prop('scrollTop') + defaultOffset;
			var scrollHeight = getScrollHeight();
			var maxScroll = defaultOffset + scrollHeight - scrollableElement.prop('offsetHeight');

			if (scrollHeight !== initScrollHeight) {
				refresh();
			}

			if (scrollTop >= maxScroll) {
				var item = scrollTop === defaultOffset ? targets[0] : targets[targets.length - 1];
				if (activeTarget !== item) {
					activateItem(item);
				}
				return;
			}

			if (activeTarget && scrollTop < offsets[0]) {
				activeTarget = null;
				clear();
				return;
			}

			for (var i = 0; i < offsets.length; i++) {
				if (scrollTopWithinGroup(scrollTop, i)) {
					activateItem(targets[i]);
					return;
				}
			}
		};

		this.activate = function (element, offset) {
			scrollableElement = element;
			defaultOffset = offset;

			element.bind('scroll', self.update);
			var window = angular.element($window);
			window.bind('resize', function () {
				$timeout(self.update, 100);
			});

			refresh();
			self.update();
		};

		this.deactivate = function (element) {
			element.unbind('scroll');
		};

		this.activateItemOnClick = function (target) {
			var scrollTop = scrollableElement.prop('scrollTop') + defaultOffset;
			var targetIndex = targets.indexOf(target);
			if (scrollTopOnScreen(scrollTop, targetIndex)) {
				activateItem(target);
			} else {
				$anchorScroll(target);
				$timeout(function () {
					if (target !== activeTarget) {
						activateItem(target);
					}
				}, 0);
			}
		};

		this.invalidate = function() {
			$element.css('position', 'static');
			$timeout(function () {
				$element.css('position', 'relative');
			}, 0);
		};
	}

	function scrollSpyDirective () {
		return {
			restrict: 'A',
			controller: scrollSpyController,
			link: function (scope, elem, attrs, ctrl) {	}
		};
	}

	function scrollSpyAreaDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				// ToDo: find solution to get calculated overflowY without jQuery
				var $elem = $(elem);

				var offset = parseInt(attrs.spyOffset);
				scope.$watch(function () {
					return $elem.css('overflow-y');
				}, function (value) {
					if (value !== 'hidden') {
						ctrl.activate(elem, offset);
					} else {
						ctrl.deactivate(elem);
						ctrl.invalidate();
					}
				});

				scope.$watch(function () {
					return elem[0].offsetHeight;
				}, function (value) {
					if ($elem.css('overflow-y') !== 'hidden') {
						ctrl.update();
					}
				});
			}
		}
	};

	function spyItemDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				ctrl.anchors[attrs.target] = elem;
				elem.bind('click', function () {
					ctrl.activateItemOnClick(attrs.target);
				});
			}
		}
	}

	function spyGroupDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				ctrl.groups[attrs.id] = elem;
			}
		}
	}
})(angular);
<|endoftext|>