<commit_before>#!/usr/bin/env node

// file system
var fs = require('fs');
// shell tools
var shell = require('shelljs');
// underscore
var _ = require('underscore');
var zipFolder = require('zip-folder');
var child_process = require('child_process');

var resourcePath = '../SolitaireResource/图片资源/UI/新主题';
var outPutPath = '../SolitaireClient/Proj.android-studio/theme/src/main';
var tmpRootPath = 'tmp';
var tmp2RootPath = 'tmp2';
var zipRootPath = 'zip';
var projScrRootPath = '../SolitaireClient/Proj.android-studio/theme/src/main';
var packageFilePath = '../SolitaireClient/scr/package.json';
var SolitaireClient_Path = '../SolitaireClient'
var SolitaireThemePakeage_Path = '../SolitaireThemePakeage'

var exists = function (path) {
  try {
    return fs.statSync(path,fs.F_OK);
  } catch (error) {
    return false;
  }
}

var copyFile = function (src,dist) {
  // console.log('复制文件：'+src+' 到 '+dist);
  var isExists = exists(src);
  if (isExists){
    // fs.createReadStream(src).pipe(fs.createWriteStream(dist));
    shell.exec('cp -f '+src+' '+dist);
  }
}

var rmDir = function (path) {
  var isExists = exists(path);
  if (isExists) {
    shell.exec('rm -rf '+path);//强制删除整个tmp文件夹
  }
}

var copyDir = function (src,dist) {
  var isExists = exists(src);
  if (isExists) {
    var distState = exists(dist);
    if (!distState){
      fs.mkdirSync(dist);//创建目标文件夹
    }
    var files = fs.readdirSync(src);
    files.forEach(function (file) {
      var _src = src + '/' + file;
      var _dist = dist + '/' + file;
      var state = exists(_src);
      if (state && state.isFile()) {
        copyFile(_src,_dist);
      }else if (state && state.isDirectory()) {
        copyDir(_src,_dist);
      }
    })
  }
}

var readThemeJsonFile = function (fileName) {
  var filePath = resourcePath + '/' + fileName;
  var files = fs.readdirSync(filePath);
  var jsonFileName = "theme_"+fileName+".json";
  var index = _.indexOf(files,jsonFileName);
  if (index === -1) {
    return undefined;
  }
  var jsonFilePath = filePath+'/'+jsonFileName;
  var themeConfig = JSON.parse(fs.readFileSync(jsonFilePath));
  return themeConfig;
}

var Operation1 = function (fileName) {
    if (fileName === '.DS_Store'){
      return;
    }
    var tmpFilePath = tmpRootPath+'/'+fileName;
    var UI_ResourcesPath = tmpFilePath+'/UI_Resources';
    fs.mkdirSync(tmpFilePath);
    fs.mkdirSync(UI_ResourcesPath);
    console.log('开始遍历文件：'+fileName);
    var filePath = resourcePath + '/' + fileName;
    var themeConfig = readThemeJsonFile(fileName);
    if (!themeConfig){
      return console.error('在%s文件夹下，没有找到可用的json配置文件',fileName);
    }
    var textureName = themeConfig.cardPlist;
    //生成大图
    shell.exec('TexturePacker --sheet ' + UI_ResourcesPath + '/' + textureName + '.png --data ' + UI_ResourcesPath + '/' + textureName + '.plist ' +filePath + '/UI_CardResources/*png');
    var jsonFileName = "theme_"+fileName+".json";
    //拷贝json文件
    copyFile(filePath+'/'+jsonFileName,UI_ResourcesPath+'/'+jsonFileName);
    //拷贝color文件
    var colorFileName = "color"+themeConfig.faceIndex+'.cardcolor';
    copyFile(filePath+'/'+colorFileName,UI_ResourcesPath+'/'+colorFileName);
    //拷贝paticle文件夹
    var paticleDirPath = filePath+'/particle';
    copyDir(paticleDirPath,tmpFilePath+'/particle');
    //拷贝theme文件夹
    var themeDirPath = filePath+'/theme';
    copyDir(themeDirPath,UI_ResourcesPath+"/theme");
    //拷贝cardstyle文件夹
    var cardstyleDirPath = filePath+'/cardstyle';
    copyDir(cardstyleDirPath,UI_ResourcesPath+"/cardstyle");
    // //进行文件加密
    var shellEx = '$QUICK_COCOS2DX_ROOT/bin/pack_files.sh -i ' + tmpFilePath + ' -o ' + tmp2RootPath + '/' + fileName + ' -ek 8VVTJ-UC2R1 -es XXTEA -x .json,.cardcolor';
    console.log(shellEx);
    shell.exec(shellEx);
}

var Operation2 = function(floders,index,callback) {
  if (floders.length <= index) {
    callback();
    return;
  }
  //生成.zip操作
  if (fileName === '.DS_Store'){
    Operation2(floders,index+1,callback);
  }else{
    var fileName = floders[index];
    var tmpFilePath = tmp2RootPath+'/'+fileName;
    zipFolder(tmpFilePath,zipRootPath+'/'+fileName+'.aac',function(error) {
      if (error){
        console.log(fileName + '打包zip失败：'+error);
        Operation2(floders,index+1,callback);
      }else{
        //修改后缀为.aac
        // fs.renameSync(oldPath, newPath)
        Operation2(floders,index+1,callback);
      }
    });
  }
}

var supportLan = ['ar','de','es','fr','id','it','ja','ko','nl','pt','ru','tr','zh-rCN','zh-rHK','zh-rTW'];

var changeAppName = function (file,themeConfig) {
  var resDirPath = projScrRootPath + '/res_' + themeConfig.key;
  var files = fs.readdirSync(resDirPath);
  changeXML(resDirPath+'/values/strings.xml',themeConfig.appName);
  supportLan.forEach(function(lan) {
    changeXML(resDirPath+'/values-'+lan+'/values-'+lan+'.xml',themeConfig.appName);
  }, this);
}

var changeXML = function (xmlPath,appName) {
  var stringsXml = fs.readFileSync(xmlPath,'utf8');
  stringsXml = stringsXml.replace(/Soccer Theme/,appName);
  fs.writeFileSync(xmlPath,stringsXml,'utf8');
}

//准备工程文件
var Operation3 = function(files) {
  console.log('开始准备工程文件');
  var packageJson = JSON.parse(fs.readFileSync(packageFilePath));
  var newThemes = {}
  files.forEach(function(file) {
    var themeConfig = readThemeJsonFile(file);
    if (themeConfig) {
      var packageKey = "com.born2play.solitaire.theme."+themeConfig.key;
      var resDirName = 'res_'+themeConfig.key;
      var assetsDirName = 'assets_'+themeConfig.key;
      //创建相关的文件夹
      var resDirPath = projScrRootPath + '/' + resDirName;
      var assetsDirPath = projScrRootPath + '/' + assetsDirName;
      console.log(file+'开始准备asset和res文件夹');
      fs.mkdirSync(assetsDirPath);
      copyDir(projScrRootPath+'/resDefault',resDirPath);
      //拷贝zip文件到assets下
      var zipFilePath = zipRootPath+'/'+file+'.aac';
      copyFile(zipFilePath,assetsDirPath+'/'+file+'.aac');
      //拷贝bg.png到res/drawable
      var bgPngFilePath = resourcePath+'/'+file+'/bg.png';
      copyFile(bgPngFilePath,resDirPath+'/drawable/bg.png');
      //拷贝icon到res文件下
      var iconDirPath = resourcePath+'/'+file+'/icon';
      var iconDirs = fs.readdirSync(iconDirPath)
      iconDirs.forEach(function(iconDir) {
        if (iconDir.indexOf('mipmap') === 0){
          copyDir(iconDirPath + '/' + iconDir, resDirPath + '/' + iconDir);
        }
      }, this);
      //修改res下value文件的appName
      changeAppName(file,themeConfig);
      //
      newThemes[packageKey] = {
        name:themeConfig.key,
        resDir:resDirName,
        assetsDir:assetsDirName
      }
    }
  }, this);
  console.log('修改工程配置文件packge.json');
  packageJson.theme = newThemes;
  fs.writeFileSync(packageFilePath,JSON.stringify(packageJson),'utf8');
}

var rmProjectResource = function() {
  var floders = fs.readdirSync(projScrRootPath);
  for (var index = 0; index < floders.length; index++) {
    var floderName = floders[index];
    var assetsIndex = floderName.indexOf('assets_');
    var resIndex = floderName.indexOf('res_');
    if (assetsIndex != -1 || resIndex != -1) {
      rmDir(projScrRootPath + '/' + floderName);
    }
  }
}

var Operation4 = function () {
  shell.exec('cd ../SolitaireClient');
  // shell.exec('./buildAPK.sh');
  // shell.exec('cd '+ SolitaireThemePakeage_Path);
}

var OperationBegin = function() {
    //1.删除原有的临时文件，创建新的文件夹
    rmDir(tmpRootPath);
    rmDir(tmp2RootPath);
    rmDir(zipRootPath);
    fs.mkdirSync(tmpRootPath);
    fs.mkdirSync(tmp2RootPath);
    fs.mkdirSync(zipRootPath);
    //读取资源文件夹
    var files = fs.readdirSync(resourcePath);
    var index = _.indexOf(files,'.DS_Store')
    if (index != -1){
      files.splice(index,1);
    }
    console.log(files);
    files.forEach(function(file) {
      Operation1(file);
    }, this);

    Operation2(files,0,function() {
      rmProjectResource();
      Operation3(files);
    })
}

OperationBegin();<commit_msg>drawable文件夹生成<commit_after>#!/usr/bin/env node

// file system
var fs = require('fs');
// shell tools
var shell = require('shelljs');
// underscore
var _ = require('underscore');
var zipFolder = require('zip-folder');
var child_process = require('child_process');

var resourcePath = '../SolitaireResource/图片资源/UI/新主题';
var outPutPath = '../SolitaireClient/Proj.android-studio/theme/src/main';
var tmpRootPath = 'tmp';
var tmp2RootPath = 'tmp2';
var zipRootPath = 'zip';
var projScrRootPath = '../SolitaireClient/Proj.android-studio/theme/src/main';
var packageFilePath = '../SolitaireClient/scr/package.json';
var SolitaireClient_Path = '../SolitaireClient'
var SolitaireThemePakeage_Path = '../SolitaireThemePakeage'

var exists = function (path) {
  try {
    return fs.statSync(path,fs.F_OK);
  } catch (error) {
    return false;
  }
}

var copyFile = function (src,dist) {
  // console.log('复制文件：'+src+' 到 '+dist);
  var isExists = exists(src);
  if (isExists){
    // fs.createReadStream(src).pipe(fs.createWriteStream(dist));
    shell.exec('cp -f '+src+' '+dist);
  }
}

var rmDir = function (path) {
  var isExists = exists(path);
  if (isExists) {
    shell.exec('rm -rf '+path);//强制删除整个tmp文件夹
  }
}

var copyDir = function (src,dist) {
  var isExists = exists(src);
  if (isExists) {
    var distState = exists(dist);
    if (!distState){
      fs.mkdirSync(dist);//创建目标文件夹
    }
    var files = fs.readdirSync(src);
    files.forEach(function (file) {
      var _src = src + '/' + file;
      var _dist = dist + '/' + file;
      var state = exists(_src);
      if (state && state.isFile()) {
        copyFile(_src,_dist);
      }else if (state && state.isDirectory()) {
        copyDir(_src,_dist);
      }
    })
  }
}

var readThemeJsonFile = function (fileName) {
  var filePath = resourcePath + '/' + fileName;
  var files = fs.readdirSync(filePath);
  var jsonFileName = "theme_"+fileName+".json";
  var index = _.indexOf(files,jsonFileName);
  if (index === -1) {
    return undefined;
  }
  var jsonFilePath = filePath+'/'+jsonFileName;
  var themeConfig = JSON.parse(fs.readFileSync(jsonFilePath));
  return themeConfig;
}

var Operation1 = function (fileName) {
    if (fileName === '.DS_Store'){
      return;
    }
    var tmpFilePath = tmpRootPath+'/'+fileName;
    var UI_ResourcesPath = tmpFilePath+'/UI_Resources';
    fs.mkdirSync(tmpFilePath);
    fs.mkdirSync(UI_ResourcesPath);
    console.log('开始遍历文件：'+fileName);
    var filePath = resourcePath + '/' + fileName;
    var themeConfig = readThemeJsonFile(fileName);
    if (!themeConfig){
      return console.error('在%s文件夹下，没有找到可用的json配置文件',fileName);
    }
    var textureName = themeConfig.cardPlist;
    //生成大图
    shell.exec('TexturePacker --sheet ' + UI_ResourcesPath + '/' + textureName + '.png --data ' + UI_ResourcesPath + '/' + textureName + '.plist ' +filePath + '/UI_CardResources/*png');
    var jsonFileName = "theme_"+fileName+".json";
    //拷贝json文件
    copyFile(filePath+'/'+jsonFileName,UI_ResourcesPath+'/'+jsonFileName);
    //拷贝color文件
    var colorFileName = "color"+themeConfig.faceIndex+'.cardcolor';
    copyFile(filePath+'/'+colorFileName,UI_ResourcesPath+'/'+colorFileName);
    //拷贝paticle文件夹
    var paticleDirPath = filePath+'/particle';
    copyDir(paticleDirPath,tmpFilePath+'/particle');
    //拷贝theme文件夹
    var themeDirPath = filePath+'/theme';
    copyDir(themeDirPath,UI_ResourcesPath+"/theme");
    //拷贝cardstyle文件夹
    var cardstyleDirPath = filePath+'/cardstyle';
    copyDir(cardstyleDirPath,UI_ResourcesPath+"/cardstyle");
    // //进行文件加密
    var shellEx = '$QUICK_COCOS2DX_ROOT/bin/pack_files.sh -i ' + tmpFilePath + ' -o ' + tmp2RootPath + '/' + fileName + ' -ek 8VVTJ-UC2R1 -es XXTEA -x .json,.cardcolor';
    console.log(shellEx);
    shell.exec(shellEx);
}

var Operation2 = function(floders,index,callback) {
  if (floders.length <= index) {
    callback();
    return;
  }
  //生成.zip操作
  if (fileName === '.DS_Store'){
    Operation2(floders,index+1,callback);
  }else{
    var fileName = floders[index];
    var tmpFilePath = tmp2RootPath+'/'+fileName;
    zipFolder(tmpFilePath,zipRootPath+'/'+fileName+'.aac',function(error) {
      if (error){
        console.log(fileName + '打包zip失败：'+error);
        Operation2(floders,index+1,callback);
      }else{
        //修改后缀为.aac
        // fs.renameSync(oldPath, newPath)
        Operation2(floders,index+1,callback);
      }
    });
  }
}

var supportLan = ['ar','de','es','fr','id','it','ja','ko','nl','pt','ru','tr','zh-rCN','zh-rHK','zh-rTW'];

var changeAppName = function (file,themeConfig) {
  var resDirPath = projScrRootPath + '/res_' + themeConfig.key;
  var files = fs.readdirSync(resDirPath);
  changeXML(resDirPath+'/values/strings.xml',themeConfig.appName);
  supportLan.forEach(function(lan) {
    changeXML(resDirPath+'/values-'+lan+'/values-'+lan+'.xml',themeConfig.appName);
  }, this);
}

var changeXML = function (xmlPath,appName) {
  var stringsXml = fs.readFileSync(xmlPath,'utf8');
  stringsXml = stringsXml.replace(/Soccer Theme/,appName);
  fs.writeFileSync(xmlPath,stringsXml,'utf8');
}

//准备工程文件
var Operation3 = function(files) {
  console.log('开始准备工程文件');
  var packageJson = JSON.parse(fs.readFileSync(packageFilePath));
  var newThemes = {}
  files.forEach(function(file) {
    var themeConfig = readThemeJsonFile(file);
    if (themeConfig) {
      var packageKey = "com.born2play.solitaire.theme."+themeConfig.key;
      var resDirName = 'res_'+themeConfig.key;
      var assetsDirName = 'assets_'+themeConfig.key;
      //创建相关的文件夹
      var resDirPath = projScrRootPath + '/' + resDirName;
      var assetsDirPath = projScrRootPath + '/' + assetsDirName;
      console.log(file+'开始准备asset和res文件夹');
      fs.mkdirSync(assetsDirPath);
      copyDir(projScrRootPath+'/resDefault',resDirPath);
      //拷贝zip文件到assets下
      var zipFilePath = zipRootPath+'/'+file+'.aac';
      copyFile(zipFilePath,assetsDirPath+'/'+file+'.aac');
      //拷贝bg.png到res/drawable
      var drawablePath = resDirPath+'/drawable';
      var isExists = exists(drawablePath);
      if (!isExists){
        fs.mkdirSync(drawablePath);
      }
      var bgPngFilePath = resourcePath+'/'+file+'/bg.png';
      copyFile(bgPngFilePath,drawablePath+'/bg.png');
      //拷贝icon到res文件下
      var iconDirPath = resourcePath+'/'+file+'/icon';
      var iconDirs = fs.readdirSync(iconDirPath)
      iconDirs.forEach(function(iconDir) {
        if (iconDir.indexOf('mipmap') === 0){
          copyDir(iconDirPath + '/' + iconDir, resDirPath + '/' + iconDir);
        }
      }, this);
      //修改res下value文件的appName
      changeAppName(file,themeConfig);
      //
      newThemes[packageKey] = {
        name:themeConfig.key,
        resDir:resDirName,
        assetsDir:assetsDirName
      }
    }
  }, this);
  console.log('修改工程配置文件packge.json');
  packageJson.theme = newThemes;
  fs.writeFileSync(packageFilePath,JSON.stringify(packageJson),'utf8');
}

var rmProjectResource = function() {
  var floders = fs.readdirSync(projScrRootPath);
  for (var index = 0; index < floders.length; index++) {
    var floderName = floders[index];
    var assetsIndex = floderName.indexOf('assets_');
    var resIndex = floderName.indexOf('res_');
    if (assetsIndex != -1 || resIndex != -1) {
      rmDir(projScrRootPath + '/' + floderName);
    }
  }
}

var Operation4 = function () {
  shell.exec('cd ../SolitaireClient');
  // shell.exec('./buildAPK.sh');
  // shell.exec('cd '+ SolitaireThemePakeage_Path);
}

var OperationBegin = function() {
    //1.删除原有的临时文件，创建新的文件夹
    rmDir(tmpRootPath);
    rmDir(tmp2RootPath);
    rmDir(zipRootPath);
    fs.mkdirSync(tmpRootPath);
    fs.mkdirSync(tmp2RootPath);
    fs.mkdirSync(zipRootPath);
    //读取资源文件夹
    var files = fs.readdirSync(resourcePath);
    var index = _.indexOf(files,'.DS_Store')
    if (index != -1){
      files.splice(index,1);
    }
    console.log(files);
    files.forEach(function(file) {
      Operation1(file);
    }, this);

    Operation2(files,0,function() {
      rmProjectResource();
      Operation3(files);
    })
}

OperationBegin();<|endoftext|>


<commit_before>(function(root) {
  function embed_document(root) {
    {% block code_to_run %}
    {% endblock %}
  }
  if (root.Bokeh !== undefined) {
    embed_document(root);
  } else {
    var attempts = 0;
    var timer = setInterval(function(root) {
      if (root.Bokeh !== undefined) {
        embed_document(root);
        clearInterval(timer);
      }
      attempts++;
      if (attempts > 100) {
        console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing")
        clearInterval(timer);
      }
    }, 10, root)
  }
})(window);
<commit_msg>Resolve #8271 by adding ";" to console.log command (#8272)<commit_after>(function(root) {
  function embed_document(root) {
    {% block code_to_run %}
    {% endblock %}
  }
  if (root.Bokeh !== undefined) {
    embed_document(root);
  } else {
    var attempts = 0;
    var timer = setInterval(function(root) {
      if (root.Bokeh !== undefined) {
        embed_document(root);
        clearInterval(timer);
      }
      attempts++;
      if (attempts > 100) {
        console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
        clearInterval(timer);
      }
    }, 10, root)
  }
})(window);
<|endoftext|>


<commit_before>/*global unexpected:true*/
unexpected = require('unexpected');
unexpected.output.preferredWidth = 80;
unexpected = unexpected.clone();
unexpected.use(require('./lib/unexpectedImage'));
<commit_msg>bootstrap-unexpected-markdown: Override preferredWidth after cloning, that really ought to work<commit_after>/*global unexpected:true*/
unexpected = require('unexpected');
unexpected = unexpected.clone();
unexpected.output.preferredWidth = 80;
unexpected.use(require('./lib/unexpectedImage'));
<|endoftext|>


<commit_before>// module.exports = {
//   /**
//    * Validate a given ID and saves the url
//    * @param {String}  id The client id
//    * @param {Function} callback The callback that will be called when the
//    * validation finishes. The callback parameters are a Boolean, that responds
//    * if it's valid and an Object with the response data
//    */
//   clientId: function(id, callback) {
//     var valid = false;
//     var response = {};
//     if (id !== 'olar') {
//       valid = true;
//       response = {
//         data: {
//           apiUrl: 'https://services.virtualkar.com.br/rest/'
//         }
//       };
//     }
//     callback(valid, response);
//   }
// };
<commit_msg>[master] Added validation<commit_after>module.exports = {
  /**
   * Validate a given ID and saves the url
   * @param {String}  id The client id
   * @param {Function} callback The callback that will be called when the
   * validation finishes. The callback parameters are a Boolean, that responds
   * if it's valid and an Object with the response data
   */
  clientId: function(id, callback) {
    var valid = false;
    var response = {};
    if (id !== 'olar') {
      valid = true;
      response = {
        data: {
          apiUrl: 'https://services.virtualkar.com.br/rest/'
        }
      };
    }
    callback(valid, response);
  }
};
<|endoftext|>


<commit_before>/**
 * `googlesitekit/modules` datastore: settings tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import Modules from 'googlesitekit-modules';
import { STORE_NAME } from './constants';
import { createTestRegistry } from '../../../../../tests/js/utils';

describe( 'core/modules store changes', () => {
	let registry;
	const slug = 'test-module';
	const nonExistentModuleSlug = 'not-module';
	const moduleStoreName = `modules/${ slug }`;
	const controlReturn = 'dummy_return_value';
	const DUMMY_ACTION = 'DUMMY_ACTION';
	let moduleCanSubmitChanges = false;
	let submittingChanges = false;

	beforeEach( () => {
		const storeDefinition = Modules.createModuleStore( moduleStoreName );
		const canSubmitChanges = () => moduleCanSubmitChanges;
		const isDoingSubmitChanges = () => {
			return submittingChanges;
		};

		registry = createTestRegistry();
		registry.registerStore( moduleStoreName, Data.combineStores(
			storeDefinition,
			{
				actions: {
					*submitChanges() {
						const result = yield {
							payload: {},
							type: DUMMY_ACTION,
						};
						return result;
					},
				},
				selectors: {
					canSubmitChanges,
					isDoingSubmitChanges,
				},
				controls: {
					[ DUMMY_ACTION ]: () => controlReturn,
				},
			}
		) );
	} );

	describe( 'actions', () => {
		describe( 'submitChanges', () => {
			it( 'proxies the dispatched action to the module with the given slug', async () => {
				const expectedError = { error: `'modules/${ nonExistentModuleSlug }' does not have a submitChanges() action.` };
				expect( await registry.dispatch( STORE_NAME ).submitChanges( nonExistentModuleSlug ) ).toEqual( expectedError );

				expect( await registry.dispatch( STORE_NAME ).submitChanges( slug ) ).toBe( controlReturn );
			} );
			it( 'checks that we can not call submitChanges without a module slug', async () => {
				expect( () => {
					registry.dispatch( STORE_NAME ).submitChanges();
				} ).toThrow();
			} );
		} );
	} );

	describe( 'selectors', () => {
		describe( 'isDoingSubmitChanges', () => {
			it( 'it proxies the selector call to the module with the given slug', async () => {
				expect( registry.select( STORE_NAME ).isDoingSubmitChanges( nonExistentModuleSlug ) ).toBe( false );

				expect( registry.select( STORE_NAME ).isDoingSubmitChanges( slug ) ).toBe( false );
				submittingChanges = true;
				expect( registry.select( STORE_NAME ).isDoingSubmitChanges( slug ) ).toBe( true );
			} );
		} );

		describe( 'canSubmitChanges', () => {
			it( 'it proxies the selector call to the module with the given slug', () => {
				expect( registry.select( STORE_NAME ).canSubmitChanges( slug ) ).toBe( false );
				moduleCanSubmitChanges = true;
				expect( registry.select( STORE_NAME ).canSubmitChanges( slug ) ).toBe( true );

				expect( registry.select( STORE_NAME ).canSubmitChanges( nonExistentModuleSlug ) ).toBe( false );
			} );
		} );
	} );
} );
<commit_msg>Apply suggestions from code review.<commit_after>/**
 * `googlesitekit/modules` datastore: settings tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import Modules from 'googlesitekit-modules';
import { STORE_NAME } from './constants';
import { createTestRegistry } from '../../../../../tests/js/utils';

describe( 'core/modules settings', () => {
	let registry;
	const slug = 'test-module';
	const nonExistentModuleSlug = 'not-module';
	const moduleStoreName = `modules/${ slug }`;
	const controlReturn = 'dummy_return_value';
	const DUMMY_ACTION = 'DUMMY_ACTION';
	let moduleCanSubmitChanges = false;
	let submittingChanges = false;

	beforeEach( () => {
		const storeDefinition = Modules.createModuleStore( moduleStoreName );
		const canSubmitChanges = () => moduleCanSubmitChanges;
		const isDoingSubmitChanges = () => {
			return submittingChanges;
		};

		registry = createTestRegistry();
		registry.registerStore( moduleStoreName, Data.combineStores(
			storeDefinition,
			{
				actions: {
					*submitChanges() {
						const result = yield {
							payload: {},
							type: DUMMY_ACTION,
						};
						return result;
					},
				},
				selectors: {
					canSubmitChanges,
					isDoingSubmitChanges,
				},
				controls: {
					[ DUMMY_ACTION ]: () => controlReturn,
				},
			}
		) );
	} );

	describe( 'actions', () => {
		describe( 'submitChanges', () => {
			it( 'proxies the dispatched action to the module with the given slug', async () => {
				const expectedError = { error: `'modules/${ nonExistentModuleSlug }' does not have a submitChanges() action.` };
				expect( await registry.dispatch( STORE_NAME ).submitChanges( nonExistentModuleSlug ) ).toEqual( expectedError );

				expect( await registry.dispatch( STORE_NAME ).submitChanges( slug ) ).toBe( controlReturn );
			} );

			it( 'checks that we can not call submitChanges without a module slug', async () => {
				expect( () => {
					registry.dispatch( STORE_NAME ).submitChanges();
				} ).toThrow();
			} );
		} );
	} );

	describe( 'selectors', () => {
		describe( 'isDoingSubmitChanges', () => {
			it( 'it proxies the selector call to the module with the given slug', async () => {
				expect( registry.select( STORE_NAME ).isDoingSubmitChanges( nonExistentModuleSlug ) ).toBe( false );

				expect( registry.select( STORE_NAME ).isDoingSubmitChanges( slug ) ).toBe( false );
				submittingChanges = true;
				expect( registry.select( STORE_NAME ).isDoingSubmitChanges( slug ) ).toBe( true );
			} );
		} );

		describe( 'canSubmitChanges', () => {
			it( 'it proxies the selector call to the module with the given slug', () => {
				expect( registry.select( STORE_NAME ).canSubmitChanges( slug ) ).toBe( false );
				moduleCanSubmitChanges = true;
				expect( registry.select( STORE_NAME ).canSubmitChanges( slug ) ).toBe( true );

				expect( registry.select( STORE_NAME ).canSubmitChanges( nonExistentModuleSlug ) ).toBe( false );
			} );
		} );
	} );
} );
<|endoftext|>


<commit_before>/**
 * AdSense Main setup component.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { useUpdateEffect } from 'react-use';
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { _x } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import ProgressBar from '../../../../../components/ProgressBar';
import AdSenseIcon from '../../../../../../svg/graphics/adsense.svg';
import SetupAccount from './SetupAccount';
import SetupCreateAccount from './SetupCreateAccount';
import SetupSelectAccount from './SetupSelectAccount';
import { trackEvent } from '../../../../../util';
import { AdBlockerWarning, ErrorNotices } from '../../common';
import { MODULES_ADSENSE } from '../../../datastore/constants';
import {
	ACCOUNT_STATUS_READY,
	ACCOUNT_STATUS_NONE,
	ACCOUNT_STATUS_MULTIPLE,
} from '../../../util/status';
import useViewContext from '../../../../../hooks/useViewContext';
const { useSelect, useDispatch } = Data;

export default function SetupMain( { finishSetup } ) {
	const viewContext = useViewContext();
	const eventCategory = `${ viewContext }_adsense`;

	const {
		clearError,
		resetAccounts,
		resetAlerts,
		resetClients,
		resetSites,
		setAccountID,
		setAccountStatus,
		submitChanges,
	} = useDispatch( MODULES_ADSENSE );

	const [
		isAwaitingBackgroundSubmit,
		setIsAwaitingBackgroundSubmit,
	] = useState( false );
	// Submit changes for determined parameters in the background when they are valid.
	const [ isSubmittingInBackground, setIsSubmittingInBackground ] = useState(
		false
	);
	const isAdBlockerActive = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).isAdBlockerActive()
	);
	const accounts = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getAccounts()
	);
	const accountID = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getAccountID()
	);
	const hasAccountIDChanged = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasSettingChanged( 'accountID' )
	);
	const hasClientIDChanged = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasSettingChanged( 'clientID' )
	);
	// Check whether settings differ from server and are valid.
	const canSubmitChanges = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).canSubmitChanges()
	);

	const clientID = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getClientID()
	);
	const accountStatus = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getAccountStatus()
	);
	const hasAccountStatusChanged = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasSettingChanged( 'accountStatus' )
	);
	const siteStatus = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getSiteStatus()
	);
	const hasSiteStatusChanged = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasSettingChanged( 'siteStatus' )
	);
	const hasErrors = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasErrors()
	);

	const account = accounts?.find( ( { _id } ) => _id === accountID );

	useUpdateEffect( () => {
		if (
			( hasAccountIDChanged && accountID !== undefined ) ||
			( hasClientIDChanged && clientID !== undefined ) ||
			( hasAccountStatusChanged && accountStatus !== undefined ) ||
			( hasSiteStatusChanged && siteStatus !== undefined )
		) {
			setIsAwaitingBackgroundSubmit( true );
		}
	}, [
		accountID,
		hasAccountIDChanged,
		clientID,
		hasClientIDChanged,
		accountStatus,
		hasAccountStatusChanged,
		siteStatus,
		hasSiteStatusChanged,
	] );

	// Update current account ID setting on-the-fly.
	useEffect( () => {
		if (
			accounts?.length === 1 &&
			( ! accountID || accounts[ 0 ]._id !== accountID )
		) {
			setAccountID( accounts[ 0 ]._id );
			// Set flag to await background submission.
			setIsAwaitingBackgroundSubmit( true );
		}
	}, [ accounts, accountID, setAccountID ] );

	// Update account status on-the-fly.
	useEffect( () => {
		if ( accounts?.length === 0 ) {
			setAccountStatus( ACCOUNT_STATUS_NONE );
		} else if ( accounts?.length > 1 && ! accountID ) {
			setAccountStatus( ACCOUNT_STATUS_MULTIPLE );
		}
	}, [ setAccountStatus, accountID, accounts ] );

	// If a background submission should happen and changes are valid to be
	// submitted, do that here. This is wrapped in a separate useEffect hook
	// and relies on isAwaitingBackgroundSubmit since the above useEffect hook
	// must not depend on canSubmitChanges, since that is also updated when
	// other than the above four settings are updated.
	useEffect( () => {
		if (
			! isAwaitingBackgroundSubmit ||
			isSubmittingInBackground ||
			! canSubmitChanges
		) {
			return;
		}

		// Set flag to false since we are gonna run the background submission
		// right now.
		setIsAwaitingBackgroundSubmit( false );

		// Set internal state for submitting in background to avoid sudden
		// rendering of a progress bar.
		( async () => {
			setIsSubmittingInBackground( true );
			await submitChanges();
			setIsSubmittingInBackground( false );
		} )();
	}, [
		isAwaitingBackgroundSubmit,
		isSubmittingInBackground,
		canSubmitChanges,
		submitChanges,
	] );

	// Reset all fetched data when user re-focuses tab.
	useEffect( () => {
		let timeout;
		let needReset = false;

		// Count 15  seconds once user focuses elsewhere.
		const countIdleTime = () => {
			timeout = global.setTimeout( () => {
				needReset = true;
			}, 15000 );
		};

		// Reset when user re-focuses after 15 seconds or more.
		const reset = () => {
			global.clearTimeout( timeout );

			// Do not reset if user has been away for less than 15 seconds.
			if ( ! needReset ) {
				return;
			}
			needReset = false;

			// Do not reset if account status has not been determined yet, or
			// if the account is approved.
			if (
				undefined === accountStatus ||
				ACCOUNT_STATUS_READY === accountStatus
			) {
				return;
			}

			// Unset any potential error.
			clearError();
			// Reset all data to force re-fetch.
			resetAccounts();
			resetAlerts();
			resetClients();
			resetSites();
		};
		global.addEventListener( 'focus', reset );
		global.addEventListener( 'blur', countIdleTime );
		return () => {
			global.removeEventListener( 'focus', reset );
			global.removeEventListener( 'blur', countIdleTime );
			global.clearTimeout( timeout );
		};
	}, [
		accountStatus,
		clearError,
		resetAccounts,
		resetAlerts,
		resetClients,
		resetSites,
	] );

	useEffect( () => {
		if ( accountStatus !== undefined ) {
			trackEvent( eventCategory, 'receive_account_state', accountStatus );
		}
	}, [ eventCategory, accountStatus ] );

	useEffect( () => {
		if ( siteStatus !== undefined ) {
			trackEvent( eventCategory, 'receive_site_state', siteStatus );
		}
	}, [ eventCategory, siteStatus ] );

	if ( accounts === undefined ) {
		return <ProgressBar />;
	}

	let viewComponent;

	if ( hasErrors ) {
		viewComponent = <ErrorNotices />;
	} else if ( ! accounts.length ) {
		viewComponent = <SetupCreateAccount />;
	} else if ( ! accountID ) {
		viewComponent = <SetupSelectAccount />;
	} else {
		viewComponent = (
			<SetupAccount account={ account } finishSetup={ finishSetup } />
		);
	}

	return (

					<AdSenseIcon width="33" height="33" />

					{ _x( 'AdSense', 'Service name', 'google-site-kit' ) }

				<AdBlockerWarning />

				{ ! isAdBlockerActive && viewComponent }

	);
}

SetupMain.propTypes = {
	finishSetup: PropTypes.func,
};
<commit_msg>Add ProgressBar to viewComponent.<commit_after>/**
 * AdSense Main setup component.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { useUpdateEffect } from 'react-use';
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { _x } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import ProgressBar from '../../../../../components/ProgressBar';
import AdSenseIcon from '../../../../../../svg/graphics/adsense.svg';
import SetupAccount from './SetupAccount';
import SetupCreateAccount from './SetupCreateAccount';
import SetupSelectAccount from './SetupSelectAccount';
import { trackEvent } from '../../../../../util';
import { AdBlockerWarning, ErrorNotices } from '../../common';
import { MODULES_ADSENSE } from '../../../datastore/constants';
import {
	ACCOUNT_STATUS_READY,
	ACCOUNT_STATUS_NONE,
	ACCOUNT_STATUS_MULTIPLE,
} from '../../../util/status';
import useViewContext from '../../../../../hooks/useViewContext';
const { useSelect, useDispatch } = Data;

export default function SetupMain( { finishSetup } ) {
	const viewContext = useViewContext();
	const eventCategory = `${ viewContext }_adsense`;

	const {
		clearError,
		resetAccounts,
		resetAlerts,
		resetClients,
		resetSites,
		setAccountID,
		setAccountStatus,
		submitChanges,
	} = useDispatch( MODULES_ADSENSE );

	const [
		isAwaitingBackgroundSubmit,
		setIsAwaitingBackgroundSubmit,
	] = useState( false );
	// Submit changes for determined parameters in the background when they are valid.
	const [ isSubmittingInBackground, setIsSubmittingInBackground ] = useState(
		false
	);
	const isAdBlockerActive = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).isAdBlockerActive()
	);
	const accounts = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getAccounts()
	);
	const accountID = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getAccountID()
	);
	const hasAccountIDChanged = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasSettingChanged( 'accountID' )
	);
	const hasClientIDChanged = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasSettingChanged( 'clientID' )
	);
	// Check whether settings differ from server and are valid.
	const canSubmitChanges = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).canSubmitChanges()
	);

	const clientID = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getClientID()
	);
	const accountStatus = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getAccountStatus()
	);
	const hasAccountStatusChanged = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasSettingChanged( 'accountStatus' )
	);
	const siteStatus = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).getSiteStatus()
	);
	const hasSiteStatusChanged = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasSettingChanged( 'siteStatus' )
	);
	const hasErrors = useSelect( ( select ) =>
		select( MODULES_ADSENSE ).hasErrors()
	);

	const account = accounts?.find( ( { _id } ) => _id === accountID );

	useUpdateEffect( () => {
		if (
			( hasAccountIDChanged && accountID !== undefined ) ||
			( hasClientIDChanged && clientID !== undefined ) ||
			( hasAccountStatusChanged && accountStatus !== undefined ) ||
			( hasSiteStatusChanged && siteStatus !== undefined )
		) {
			setIsAwaitingBackgroundSubmit( true );
		}
	}, [
		accountID,
		hasAccountIDChanged,
		clientID,
		hasClientIDChanged,
		accountStatus,
		hasAccountStatusChanged,
		siteStatus,
		hasSiteStatusChanged,
	] );

	// Update current account ID setting on-the-fly.
	useEffect( () => {
		if (
			accounts?.length === 1 &&
			( ! accountID || accounts[ 0 ]._id !== accountID )
		) {
			setAccountID( accounts[ 0 ]._id );
			// Set flag to await background submission.
			setIsAwaitingBackgroundSubmit( true );
		}
	}, [ accounts, accountID, setAccountID ] );

	// Update account status on-the-fly.
	useEffect( () => {
		if ( accounts?.length === 0 ) {
			setAccountStatus( ACCOUNT_STATUS_NONE );
		} else if ( accounts?.length > 1 && ! accountID ) {
			setAccountStatus( ACCOUNT_STATUS_MULTIPLE );
		}
	}, [ setAccountStatus, accountID, accounts ] );

	// If a background submission should happen and changes are valid to be
	// submitted, do that here. This is wrapped in a separate useEffect hook
	// and relies on isAwaitingBackgroundSubmit since the above useEffect hook
	// must not depend on canSubmitChanges, since that is also updated when
	// other than the above four settings are updated.
	useEffect( () => {
		if (
			! isAwaitingBackgroundSubmit ||
			isSubmittingInBackground ||
			! canSubmitChanges
		) {
			return;
		}

		// Set flag to false since we are gonna run the background submission
		// right now.
		setIsAwaitingBackgroundSubmit( false );

		// Set internal state for submitting in background to avoid sudden
		// rendering of a progress bar.
		( async () => {
			setIsSubmittingInBackground( true );
			await submitChanges();
			setIsSubmittingInBackground( false );
		} )();
	}, [
		isAwaitingBackgroundSubmit,
		isSubmittingInBackground,
		canSubmitChanges,
		submitChanges,
	] );

	// Reset all fetched data when user re-focuses tab.
	useEffect( () => {
		let timeout;
		let needReset = false;

		// Count 15  seconds once user focuses elsewhere.
		const countIdleTime = () => {
			timeout = global.setTimeout( () => {
				needReset = true;
			}, 15000 );
		};

		// Reset when user re-focuses after 15 seconds or more.
		const reset = () => {
			global.clearTimeout( timeout );

			// Do not reset if user has been away for less than 15 seconds.
			if ( ! needReset ) {
				return;
			}
			needReset = false;

			// Do not reset if account status has not been determined yet, or
			// if the account is approved.
			if (
				undefined === accountStatus ||
				ACCOUNT_STATUS_READY === accountStatus
			) {
				return;
			}

			// Unset any potential error.
			clearError();
			// Reset all data to force re-fetch.
			resetAccounts();
			resetAlerts();
			resetClients();
			resetSites();
		};
		global.addEventListener( 'focus', reset );
		global.addEventListener( 'blur', countIdleTime );
		return () => {
			global.removeEventListener( 'focus', reset );
			global.removeEventListener( 'blur', countIdleTime );
			global.clearTimeout( timeout );
		};
	}, [
		accountStatus,
		clearError,
		resetAccounts,
		resetAlerts,
		resetClients,
		resetSites,
	] );

	useEffect( () => {
		if ( accountStatus !== undefined ) {
			trackEvent( eventCategory, 'receive_account_state', accountStatus );
		}
	}, [ eventCategory, accountStatus ] );

	useEffect( () => {
		if ( siteStatus !== undefined ) {
			trackEvent( eventCategory, 'receive_site_state', siteStatus );
		}
	}, [ eventCategory, siteStatus ] );

	let viewComponent;

	if ( accounts === undefined ) {
		viewComponent = <ProgressBar />;
	} else if ( hasErrors ) {
		viewComponent = <ErrorNotices />;
	} else if ( ! accounts.length ) {
		viewComponent = <SetupCreateAccount />;
	} else if ( ! accountID ) {
		viewComponent = <SetupSelectAccount />;
	} else {
		viewComponent = (
			<SetupAccount account={ account } finishSetup={ finishSetup } />
		);
	}

	return (

					<AdSenseIcon width="33" height="33" />

					{ _x( 'AdSense', 'Service name', 'google-site-kit' ) }

				<AdBlockerWarning />

				{ ! isAdBlockerActive && viewComponent }

	);
}

SetupMain.propTypes = {
	finishSetup: PropTypes.func,
};
<|endoftext|>


<commit_before>// Inspiration/Helpers
// http://stackoverflow.com/questions/5904914/javascript-regex-to-replace-text-not-in-html-attributes/5904945#5904945
// http://stackoverflow.com/questions/15604140/replace-multiple-strings-with-multiple-other-strings

var StringReplacer = function( map, config ){
	if( typeof map !== 'undefined' ){
		this.wordMap = map;
		this.regex = this.createRegex();
	}
};

StringReplacer.prototype = {
	run: function( startNode ){
		this.walk( startNode );
	},

	set: function( key, value ){
		this[key] = value;
		if( key === 'wordMap' ){
			this.regex = this.createRegex();
		}
	},

	createRegex: function(){
		var keysQuery = Object.keys(this.wordMap).join('|');
		var regexStr = '\\b(' + keysQuery + ')\\b';
		return new RegExp(regexStr, 'gi');
	},

	walk: function( node ){
		var child, next;

		switch ( node.nodeType ){
			case 1:  // Element
			case 9:  // Document
			case 11: // Document fragment
				child = node.firstChild;

				while( child ){
					next = child.nextSibling;
					this.walk( child );
					child = next;
				}

				break;

			case 3: // Text node
				this.handleTextNode( node );
				break;
		}
	},

	handleTextNode: function( textNode ){
		textNode.nodeValue = this.replaceWords(textNode.nodeValue);
	},

	replaceWords: function( str ){
		var wordMap = this.wordMap;

		return str.replace(this.regex, function( matchedStr, foundStr ){
			var replacementStr = wordMap[foundStr.toLowerCase()];
			var result = matchedStr.replace(foundStr, replacementStr);

			if( matchedStr.charAt(0) === matchedStr.charAt(0).toUpperCase() ){
				result = result.charAt(0).toUpperCase() + result.slice(1);
			}

			return result;
		});
	}
};

var WordBird = function(){
	this.strReplacer = new StringReplacer();
	this.run();
	chrome.storage.onChanged.addListener( this.onStorageChange.bind(this) );
};

WordBird.prototype = {
	run: function(){
		var self = this;

		console.log('in WordBird.run')

		if( this.hasBeenRun === true ){
			return;
		}

		self.getFromStorage('isEnabled', function( isEnabled ){
			if( isEnabled ){
				self.getFromStorage('wordMap', function( wordMap ){
					console.log('triggered WordBird');
					self.hasBeenRun = true;
					self.strReplacer.set('wordMap', wordMap);
					self.strReplacer.run( document.body );
				});
			}
		});
	},

	disable: function(){
		console.log('WordBird needs to reload');
		// if( window.confirm('WirdBird needs to refresh') ){
		// 	window.location.reload( false );
		// }
	},

	update: function( opts ){
		if( opts.trigger === 'onStorageChange' ){
			if( opts.key === 'isEnabled' ){
				if( opts.newValue === true ){
					this.run();
				} else {
					this.disable();
				}
			} else if( opts.key === 'wordMap' ){
				this.hasBeenRun = false;
			}
		}
	},

	getFromStorage: function( key, cb ){
		chrome.storage.sync.get(key, function( data ){
			return cb( data[key] );
		});
	},

	onStorageChange: function(changes, namespace) {
		for (var key in changes) {
			var storageData = changes[key];
			if( key === 'isEnabled' || key === 'wordMap' ){
				this.update({
					trigger: 'onStorageChange',
					key: key,
					newValue: storageData.newValue,
					oldValue: storageData.oldValue
				});
			}
		}
	}
};

new WordBird();
<commit_msg>Removed logs<commit_after>// Inspiration/Helpers
// http://stackoverflow.com/questions/5904914/javascript-regex-to-replace-text-not-in-html-attributes/5904945#5904945
// http://stackoverflow.com/questions/15604140/replace-multiple-strings-with-multiple-other-strings

var StringReplacer = function( map, config ){
	if( typeof map !== 'undefined' ){
		this.wordMap = map;
		this.regex = this.createRegex();
	}
};

StringReplacer.prototype = {
	run: function( startNode ){
		this.walk( startNode );
	},

	set: function( key, value ){
		this[key] = value;
		if( key === 'wordMap' ){
			this.regex = this.createRegex();
		}
	},

	createRegex: function(){
		var keysQuery = Object.keys(this.wordMap).join('|');
		var regexStr = '\\b(' + keysQuery + ')\\b';
		return new RegExp(regexStr, 'gi');
	},

	walk: function( node ){
		var child, next;

		switch ( node.nodeType ){
			case 1:  // Element
			case 9:  // Document
			case 11: // Document fragment
				child = node.firstChild;

				while( child ){
					next = child.nextSibling;
					this.walk( child );
					child = next;
				}

				break;

			case 3: // Text node
				this.handleTextNode( node );
				break;
		}
	},

	handleTextNode: function( textNode ){
		textNode.nodeValue = this.replaceWords(textNode.nodeValue);
	},

	replaceWords: function( str ){
		var wordMap = this.wordMap;

		return str.replace(this.regex, function( matchedStr, foundStr ){
			var replacementStr = wordMap[foundStr.toLowerCase()];
			var result = matchedStr.replace(foundStr, replacementStr);

			if( matchedStr.charAt(0) === matchedStr.charAt(0).toUpperCase() ){
				result = result.charAt(0).toUpperCase() + result.slice(1);
			}

			return result;
		});
	}
};

var WordBird = function(){
	this.strReplacer = new StringReplacer();
	this.run();
	chrome.storage.onChanged.addListener( this.onStorageChange.bind(this) );
};

WordBird.prototype = {
	run: function(){
		var self = this;

		if( this.hasBeenRun === true ){
			return;
		}

		self.getFromStorage('isEnabled', function( isEnabled ){
			if( isEnabled ){
				self.getFromStorage('wordMap', function( wordMap ){
					self.hasBeenRun = true;
					self.strReplacer.set('wordMap', wordMap);
					self.strReplacer.run( document.body );
				});
			}
		});
	},

	disable: function(){
		// if( window.confirm('WirdBird needs to refresh') ){
		// 	window.location.reload( false );
		// }
	},

	update: function( opts ){
		if( opts.trigger === 'onStorageChange' ){
			if( opts.key === 'isEnabled' ){
				if( opts.newValue === true ){
					this.run();
				} else {
					this.disable();
				}
			} else if( opts.key === 'wordMap' ){
				this.hasBeenRun = false;
			}
		}
	},

	getFromStorage: function( key, cb ){
		chrome.storage.sync.get(key, function( data ){
			return cb( data[key] );
		});
	},

	onStorageChange: function(changes, namespace) {
		for (var key in changes) {
			var storageData = changes[key];
			if( key === 'isEnabled' || key === 'wordMap' ){
				this.update({
					trigger: 'onStorageChange',
					key: key,
					newValue: storageData.newValue,
					oldValue: storageData.oldValue
				});
			}
		}
	}
};

new WordBird();
<|endoftext|>


<commit_before>#!/usr/bin/env node

/* eslint max-len: 0 */
import _ from 'lodash';
import fs from 'fs';
import path from 'path';
import findImports from 'find-imports';

// Copy necessary properties from 'package.json' to 'src/package.json'
import pkg from './package.json';
import pkgApp from './src/package.json';

const files = [
    'src/*.js',
    'src/app/**/*.{js,jsx}'
];
const deps = [
    'babel-runtime', // 'babel-runtime' is required for desktop app
    'debug' // 'debug' is required for desktop app
].concat(findImports(files, { flatten: true })).sort();

pkgApp.name = pkg.name;
pkgApp.version = pkg.version;
pkgApp.homepage = pkg.homepage;
pkgApp.author = pkg.author;
pkgApp.license = pkg.license;
pkgApp.repository = pkg.repository;

// Copy only Node.js dependencies to application package.json
pkgApp.dependencies = _.pick(pkg.dependencies, deps);

const target = path.resolve(__dirname, 'src/package.json');
const content = JSON.stringify(pkgApp, null, 2);
fs.writeFileSync(target, content + '\n', 'utf8');
<commit_msg>Update package-update.js<commit_after>/* eslint max-len: 0 */
import _ from 'lodash';
import fs from 'fs';
import path from 'path';
import findImports from 'find-imports';

// Copy necessary properties from 'package.json' to 'src/package.json'
import pkg from './package.json';
import pkgApp from './src/package.json';

const files = [
    'src/*.js',
    'src/app/**/*.{js,jsx}'
];
const deps = [
    'babel-runtime', // 'babel-runtime' is required for desktop app
    'debug' // 'debug' is required for desktop app
].concat(findImports(files, { flatten: true })).sort();

pkgApp.name = pkg.name;
pkgApp.version = pkg.version;
pkgApp.homepage = pkg.homepage;
pkgApp.author = pkg.author;
pkgApp.license = pkg.license;
pkgApp.repository = pkg.repository;

// Copy only Node.js dependencies to application package.json
pkgApp.dependencies = _.pick(pkg.dependencies, deps);

const target = path.resolve(__dirname, 'src/package.json');
const content = JSON.stringify(pkgApp, null, 2);
fs.writeFileSync(target, content + '\n', 'utf8');
<|endoftext|>


<commit_before>import { mochaInstance } from 'meteor/practicalmeteor:mocha-core';
import { startBrowser } from 'meteor/meteortesting:browser-tests';

import setArgs from './runtimeArgs';

const { mochaOptions, runnerOptions } = setArgs();
const { grep, invert, reporter, serverReporter, xUnitOutput } = mochaOptions || {};

// Since intermingling client and server log lines would be confusing,
// the idea here is to buffer all client logs until server tests have
// finished running and then dump the buffer to the screen and continue
// logging in real time after that if client tests are still running.

let serverTestsDone = false;
const clientLines = [];
function clientLogBuffer(line) {
  if (serverTestsDone) {
    // printing and removing the extra new-line character. The first was added by the client log, the second here.
    console.log(line.replace(/\n$/, ''));
  } else {
    clientLines.push(line);
  }
}

function printHeader(type) {
  const lines = [
    '\n--------------------------------',
    Meteor.isAppTest ? `--- RUNNING APP ${type} TESTS ---` : `----- RUNNING ${type} TESTS -----`,
    '--------------------------------\n',
  ];
  lines.forEach((line) => {
    if (type === 'CLIENT') {
      clientLogBuffer(line);
    } else {
      console.log(line);
    }
  });
}

let callCount = 0;
let clientFailures = 0;
let serverFailures = 0;
function exitIfDone(type, failures) {
  callCount++;
  if (type === 'client') {
    clientFailures = failures;
  } else {
    serverFailures = failures;
    serverTestsDone = true;
    clientLines.forEach((line) => {
      // printing and removing the extra new-line character. The first was added by the client log, the second here.
      console.log(line.replace(/\n$/, ''));
    });
  }

  if (callCount === 2) {
    // We only need to show this final summary if we ran both kinds of tests in the same console
    if (runnerOptions.runServer && runnerOptions.runClient && runnerOptions.browserDriver) {
      console.log('All tests finished!\n');
      console.log('--------------------------------');
      console.log(`${Meteor.isAppTest ? 'APP ' : ''}SERVER FAILURES: ${serverFailures}`);
      console.log(`${Meteor.isAppTest ? 'APP ' : ''}CLIENT FAILURES: ${clientFailures}`);
      console.log('--------------------------------');
    }

    // if no env for TEST_WATCH, tests should exit when done
    if (!runnerOptions.testWatch) {
      if (clientFailures === null || serverFailures === null || clientFailures + serverFailures > 0) {
        process.exit(1); // exit with non-zero status if there were failures
      } else {
        process.exit(0);
      }
    }
  }
}

function serverTests(cb) {
  if (!runnerOptions.runServer) {
    console.log('SKIPPING SERVER TESTS BECAUSE TEST_SERVER=0');
    exitIfDone('server', 0);
    if (cb) cb();
    return;
  }

  printHeader('SERVER');

  if (grep) mochaInstance.grep(grep);
  if (invert) mochaInstance.options.invert = invert;
  mochaInstance.options.useColors = true;

  // We need to set the reporter when the tests actually run to ensure no conflicts with
  // other test driver packages that may be added to the app but are not actually being
  // used on this run.
  mochaInstance.reporter(serverReporter || reporter, {
    output: xUnitOutput,
  });

  mochaInstance.run((failureCount) => {
    exitIfDone('server', failureCount);
    if (cb) cb();
  });
}

function clientTests() {
  if (!runnerOptions.runClient) {
    console.log('SKIPPING CLIENT TESTS BECAUSE TEST_CLIENT=0');
    exitIfDone('client', 0);
    return;
  }

  if (!runnerOptions.browserDriver) {
    console.log('Load the app in a browser to run client tests, or set the TEST_BROWSER_DRIVER environment variable. ' +
      'See https://github.com/meteortesting/meteor-mocha/blob/master/README.md#run-app-tests');
    exitIfDone('client', 0);
    return;
  }

  printHeader('CLIENT');

  startBrowser({
    stdout(data) {
      clientLogBuffer(data.toString());
    },
    stderr(data) {
      clientLogBuffer(data.toString());
    },
    done(failureCount) {
      exitIfDone('client', failureCount);
    },
  });
}

// Before Meteor calls the `start` function, app tests will be parsed and loaded by Mocha
function start() {
  // Run in PARALLEL or SERIES
  // Running in series is a better default since it avoids db and state conflicts for newbs.
  // If you want parallel you will know these risks.
  if (runnerOptions.runParallel) {
    console.log('Warning: Running in parallel can cause side-effects from state/db sharing');

    serverTests();
    clientTests();
  } else {
    serverTests(() => {
      clientTests();
    });
  }
}

export { start };
<commit_msg>Move typeof guard into test run callbacks and log a helpful message if `failureCount` is not a number.<commit_after>import { mochaInstance } from 'meteor/practicalmeteor:mocha-core';
import { startBrowser } from 'meteor/meteortesting:browser-tests';

import setArgs from './runtimeArgs';

const { mochaOptions, runnerOptions } = setArgs();
const { grep, invert, reporter, serverReporter, xUnitOutput } = mochaOptions || {};

// Since intermingling client and server log lines would be confusing,
// the idea here is to buffer all client logs until server tests have
// finished running and then dump the buffer to the screen and continue
// logging in real time after that if client tests are still running.

let serverTestsDone = false;
const clientLines = [];
function clientLogBuffer(line) {
  if (serverTestsDone) {
    // printing and removing the extra new-line character. The first was added by the client log, the second here.
    console.log(line.replace(/\n$/, ''));
  } else {
    clientLines.push(line);
  }
}

function printHeader(type) {
  const lines = [
    '\n--------------------------------',
    Meteor.isAppTest ? `--- RUNNING APP ${type} TESTS ---` : `----- RUNNING ${type} TESTS -----`,
    '--------------------------------\n',
  ];
  lines.forEach((line) => {
    if (type === 'CLIENT') {
      clientLogBuffer(line);
    } else {
      console.log(line);
    }
  });
}

let callCount = 0;
let clientFailures = 0;
let serverFailures = 0;
function exitIfDone(type, failures) {
  callCount++;
  if (type === 'client') {
    clientFailures = failures;
  } else {
    serverFailures = failures;
    serverTestsDone = true;
    clientLines.forEach((line) => {
      // printing and removing the extra new-line character. The first was added by the client log, the second here.
      console.log(line.replace(/\n$/, ''));
    });
  }

  if (callCount === 2) {
    // We only need to show this final summary if we ran both kinds of tests in the same console
    if (runnerOptions.runServer && runnerOptions.runClient && runnerOptions.browserDriver) {
      console.log('All tests finished!\n');
      console.log('--------------------------------');
      console.log(`${Meteor.isAppTest ? 'APP ' : ''}SERVER FAILURES: ${serverFailures}`);
      console.log(`${Meteor.isAppTest ? 'APP ' : ''}CLIENT FAILURES: ${clientFailures}`);
      console.log('--------------------------------');
    }

    // if no env for TEST_WATCH, tests should exit when done
    if (!runnerOptions.testWatch) {
      if (clientFailures + serverFailures > 0) {
        process.exit(1); // exit with non-zero status if there were failures
      } else {
        process.exit(0);
      }
    }
  }
}

function serverTests(cb) {
  if (!runnerOptions.runServer) {
    console.log('SKIPPING SERVER TESTS BECAUSE TEST_SERVER=0');
    exitIfDone('server', 0);
    if (cb) cb();
    return;
  }

  printHeader('SERVER');

  if (grep) mochaInstance.grep(grep);
  if (invert) mochaInstance.options.invert = invert;
  mochaInstance.options.useColors = true;

  // We need to set the reporter when the tests actually run to ensure no conflicts with
  // other test driver packages that may be added to the app but are not actually being
  // used on this run.
  mochaInstance.reporter(serverReporter || reporter, {
    output: xUnitOutput,
  });

  mochaInstance.run((failureCount) => {
    if (typeof failureCount !== 'number') {
      console.log('Mocha did not return a failure count for server tests as expected');
      exitIfDone('server', 1);
    } else {
      exitIfDone('server', failureCount);
    }
    if (cb) cb();
  });
}

function clientTests() {
  if (!runnerOptions.runClient) {
    console.log('SKIPPING CLIENT TESTS BECAUSE TEST_CLIENT=0');
    exitIfDone('client', 0);
    return;
  }

  if (!runnerOptions.browserDriver) {
    console.log('Load the app in a browser to run client tests, or set the TEST_BROWSER_DRIVER environment variable. ' +
      'See https://github.com/meteortesting/meteor-mocha/blob/master/README.md#run-app-tests');
    exitIfDone('client', 0);
    return;
  }

  printHeader('CLIENT');

  startBrowser({
    stdout(data) {
      clientLogBuffer(data.toString());
    },
    stderr(data) {
      clientLogBuffer(data.toString());
    },
    done(failureCount) {
      if (typeof failureCount !== 'number') {
        console.log('The browser driver package did not return a failure count for server tests as expected');
        exitIfDone('client', 1);
      } else {
        exitIfDone('client', failureCount);
      }
    },
  });
}

// Before Meteor calls the `start` function, app tests will be parsed and loaded by Mocha
function start() {
  // Run in PARALLEL or SERIES
  // Running in series is a better default since it avoids db and state conflicts for newbs.
  // If you want parallel you will know these risks.
  if (runnerOptions.runParallel) {
    console.log('Warning: Running in parallel can cause side-effects from state/db sharing');

    serverTests();
    clientTests();
  } else {
    serverTests(() => {
      clientTests();
    });
  }
}

export { start };
<|endoftext|>


<commit_before>/*
Copyright (c) 2009-2016, Dan "Ducky" Little & GeoMOOSE.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/*
 * Class: ResultsAsGrid
 *
 * Renders the results of the query as an interactive grid.
 *
 * WARNING! This class will not work out of the box! It is necessary
 *          to create a subclass!!!
 *
 */

dojo.require('dojo.store.Memory');
dojo.require('dojo.data.ItemFileWriteStore');
dojo.require('dojo.data.ObjectStore');
dojo.require('dojox.grid.DataGrid');
dojo.require("dojox.grid._CheckBoxSelector")
dojo.require('dijit.Toolbar');
dojo.require('dijit.layout.BorderContainer');

dojo.provide("extensions.ResultsAsGrid");
dojo.declare("extensions.ResultsAsGrid", null, {
	load: function() {
		var self = this;

		// Install the required CSS for datagrid.
		var head = document.getElementsByTagName('head')[0];
		var style_urls = [
			'libs/dojo/dojox/grid/resources/Grid.css',
			'libs/dojo/dojox/grid/resources/tundraGrid.css'
		];

		for(var i = 0 ; i < style_urls.length; i++) {
			//<link type="text/css" rel="stylesheet" href="css/user_tools.css"/>
			var link = document.createElement('link');
			link.setAttribute('type', 'text/css');
			link.setAttribute('rel', 'stylesheet');
			link.setAttribute('href', style_urls[i]);
			head.appendChild(link);
		}

		//this.data = new dojo.data.ObjectStore({objectStore: this.dataStore});
		GeoMOOSE.register('onMapbookLoaded', this, this.startup);
	},

	conf: {
		targetLayer: 'vector_highlight'
	},

	/** Tracking time. */
	lastUpdate: 0,

	/** Update interval time. Every half-second. */
	updateInterval: 500,

	/** The current update interval */
	interval: null,

	gridStructure: null,

	/** This is a demo function that collects all of the selected rows and
	 *  displays the list of PIN numbers in a dialog.  This can serve
	 *  as a starting point for a service that may print mailing labels,
	 *  report on the parcels, all sorts of fun options.
	 */

	demoListParcels: function() {
		var pins = [];
		var items = this.dataGrid.selection.getSelected();
		for(var i = 0, ii = items.length; i < ii; i++) {
			var pin = this.objectStore.getValue(items[i], "PIN");
			pins.push(pin);
		}

		alert('You have selected the following PINs: '+pins.join(', '));
	},

	/** Render the toolbar, override with custom code when you need a toolbar.
	 */
	renderToolbar: function(layout) {
		/* Below is an example of how to add a button to the toolbar,
		 * WARNING! OVERRIDE THIS CLASS, DO NOT MODIFY IT!
		// add the toolbar to the layout containing the grid.
		var toolbar = new dijit.Toolbar({region: 'top'});
		layout.addChild(toolbar);
		toolbar.startup();

		var list_button = new dijit.form.Button({
			label: "List Parcels",
			iconClass: "dijitEditorIcon dijitEditorIconInsertTable",
			onClick: dojo.hitch(this, this.demoListParcels)
		});
		toolbar.addChild(list_button);
		toolbar.startup();
		*/
	},

	highlightFeatures: [],

	clearHighlightFeatures: function() {
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		ol_layer.removeFeatures(this.highlightFeatures, {silent: true});
	},

	/** Highlight the feature when the mouse if over the row.
	 */
	mouseOver: function(e) {
		var row = e.rowIndex;
		var item = this.dataGrid.getItem(row);

		//this.clearHighlightFeatures();

		if(item) {
			var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
			var f = ol_layer.getFeatureById(item.id);
			var clone = f.clone();

			clone.style =  {
				strokeColor: 'red',
				strokeOpacity: 1.0,
				fillColor: 'red',
				fillOpacity: 0.8
			};

			this.highlightFeatures.push(clone);

			ol_layer.addFeatures([clone], {silent: true});

			//ol_layer.redraw();
		}
	},

	/** Clear all 'highlighting' when the mouse leaves the grid.
	 */
	mouseOut: function(e) {
		this.clearHighlightFeatures();
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		ol_layer.redraw();
	},

	/** Style a selected feature.
	 *
	 */
	styleSelectedFeature: function(f) {
		f.style = {
			strokeColor: 'blue',
			strokeOpacity: .75,
			fillColor: 'blue',
			fillOpacity: .5
		};
	},

	/** Find the selected features and style
	 *  them to show what is "checked".
	 */
	renderSelected: function() {
		// get the OpenLayers layer class.
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		// get the list of selected "items"
		var items = this.dataGrid.selection.getSelected();
		for(var i = 0, len = items.length; i < len; i++) {
			if(items[i]) {
				var item = items[i];
				var f = ol_layer.getFeatureById(item.id);
				if(f) {
					this.styleSelectedFeature(f);
					ol_layer.drawFeature(f);
				}
			}
		}
	},

	/** Delayed update when lists are changed.
	 */
	updateGrid: function() {
		var now = (new Date()).getTime();
		// check to see if an update is appropriate
		if(now - this.lastUpdate > this.updateInterval) {
			// clear out the repeating inveral
			clearInterval(this.interval);
			this.interval = null;

			// get the count
			var n_elements = 0;
			function showCount(size, req) {
				n_elements = size;
			}
			this.objectStore.fetch({query: {}, onBegin: showCount, start: 0, count: 0});

			if(n_elements == 0) {
				// clear out the data grid if it exists.
				if(this.layout) {
					var middle = dijit.byId('middle');
					this.layout.removeChild(this.dataGrid);
					this.dataGrid = null;

					middle.removeChild(this.layout);
				}
			} else {
				if(this.dataGrid == null) {
					var middle = dijit.byId('middle');

					if(!this.layout) {
						var dg_layout = new dijit.layout.BorderContainer({
							region: 'bottom', gutters: false,
							splitter: true, liveSplitters: false,
							style: "height: 30%" //125px"
						});

						this.layout = dg_layout;
						middle.addChild(this.layout);

						this.renderToolbar(this.layout);
					}

					this.dataGrid = new dojox.grid.DataGrid({
						region: 'center',
						store: this.objectStore,
						query: {id: '*' },
						structure: this.gridStructure
					});

					dojo.connect(this.dataGrid, 'onMouseOver', this, this.mouseOver);
					dojo.connect(this.dataGrid, 'onMouseOut', this, this.mouseOut);
					dojo.connect(this.dataGrid, 'onSelectionChanged', this, this.renderSelected);

					this.layout.addChild(this.dataGrid);
					this.dataGrid.startup();
					middle.resize();
				}

				this.dataGrid.setQuery({id: '*'});
			}
		}
	},

	triggerUpdate: function() {
		this.lastUpdate = (new Date()).getTime();
		if(this.interval == null) {
			this.interval = setInterval(dojo.hitch(this, this.updateGrid), 400);
		}
	},

	startup: function() {
		// NOTE: This is a bit hacky, whenever possible directly interacting
		//       with an OpenLayers class should be avoided.  That said, I needed
		//       to access the events object.
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;

		this.memoryStore = new dojo.store.Memory({data: []});
		this.objectStore = new dojo.data.ObjectStore({objectStore: this.memoryStore});

		// this will bridge the gap between the openlayers events/features
		//  and the Dojo dataStore object.
		ol_layer.events.register('featureadded', this, function(evt) {
			var obj = evt.feature.attributes;
			// bridge the ID from OpenLayers
			obj['id'] = evt.feature.id;
			this.memoryStore.add(obj, {});

			if(this.gridStructure == null) {
				var view = [
					new dojox.grid.cells.RowIndex({ width: "20px" })
				];

				for(var attr in evt.feature.attributes) {
					// ACHTUNG! Skip the internal identifier.
					//  This could potentially be a bug where the
					//  features have an "id" column.
					console.log('Attributes', attr);
					if(attr != 'id') {
						view.push({
							name: attr, field: attr, width: 8
						});
					}
				}

				var structure = [{
					type: "dojox.grid._CheckBoxSelector"
				}, view];

				this.gridStructure = structure;

				// remove the old 'grid' and null it out
				//  so that it will be redrawn.
				if(this.dataGrid != null) {
					this.layout.removeChild(this.dataGrid);
					this.dataGrid = null;
				}

			}
			this.triggerUpdate();
		});

		ol_layer.events.register('featureremoved', this, function(evt) {
			this.memoryStore.remove(evt.feature.id);
			this.triggerUpdate();

			// when the feature are cleared out, reset the grid structure.
			if(this.memoryStore.query(function() { return true; }).total == 0) {
				this.gridStructure = null;
			}

		});

	}
});

<commit_msg>Oops, removed console.log, refs: geomoose/geomoose#147<commit_after>/*
Copyright (c) 2009-2016, Dan "Ducky" Little & GeoMOOSE.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/*
 * Class: ResultsAsGrid
 *
 * Renders the results of the query as an interactive grid.
 *
 * WARNING! This class will not work out of the box! It is necessary
 *          to create a subclass!!!
 *
 */

dojo.require('dojo.store.Memory');
dojo.require('dojo.data.ItemFileWriteStore');
dojo.require('dojo.data.ObjectStore');
dojo.require('dojox.grid.DataGrid');
dojo.require("dojox.grid._CheckBoxSelector")
dojo.require('dijit.Toolbar');
dojo.require('dijit.layout.BorderContainer');

dojo.provide("extensions.ResultsAsGrid");
dojo.declare("extensions.ResultsAsGrid", null, {
	load: function() {
		var self = this;

		// Install the required CSS for datagrid.
		var head = document.getElementsByTagName('head')[0];
		var style_urls = [
			'libs/dojo/dojox/grid/resources/Grid.css',
			'libs/dojo/dojox/grid/resources/tundraGrid.css'
		];

		for(var i = 0 ; i < style_urls.length; i++) {
			//<link type="text/css" rel="stylesheet" href="css/user_tools.css"/>
			var link = document.createElement('link');
			link.setAttribute('type', 'text/css');
			link.setAttribute('rel', 'stylesheet');
			link.setAttribute('href', style_urls[i]);
			head.appendChild(link);
		}

		//this.data = new dojo.data.ObjectStore({objectStore: this.dataStore});
		GeoMOOSE.register('onMapbookLoaded', this, this.startup);
	},

	conf: {
		targetLayer: 'vector_highlight'
	},

	/** Tracking time. */
	lastUpdate: 0,

	/** Update interval time. Every half-second. */
	updateInterval: 500,

	/** The current update interval */
	interval: null,

	gridStructure: null,

	/** This is a demo function that collects all of the selected rows and
	 *  displays the list of PIN numbers in a dialog.  This can serve
	 *  as a starting point for a service that may print mailing labels,
	 *  report on the parcels, all sorts of fun options.
	 */

	demoListParcels: function() {
		var pins = [];
		var items = this.dataGrid.selection.getSelected();
		for(var i = 0, ii = items.length; i < ii; i++) {
			var pin = this.objectStore.getValue(items[i], "PIN");
			pins.push(pin);
		}

		alert('You have selected the following PINs: '+pins.join(', '));
	},

	/** Render the toolbar, override with custom code when you need a toolbar.
	 */
	renderToolbar: function(layout) {
		/* Below is an example of how to add a button to the toolbar,
		 * WARNING! OVERRIDE THIS CLASS, DO NOT MODIFY IT!
		// add the toolbar to the layout containing the grid.
		var toolbar = new dijit.Toolbar({region: 'top'});
		layout.addChild(toolbar);
		toolbar.startup();

		var list_button = new dijit.form.Button({
			label: "List Parcels",
			iconClass: "dijitEditorIcon dijitEditorIconInsertTable",
			onClick: dojo.hitch(this, this.demoListParcels)
		});
		toolbar.addChild(list_button);
		toolbar.startup();
		*/
	},

	highlightFeatures: [],

	clearHighlightFeatures: function() {
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		ol_layer.removeFeatures(this.highlightFeatures, {silent: true});
	},

	/** Highlight the feature when the mouse if over the row.
	 */
	mouseOver: function(e) {
		var row = e.rowIndex;
		var item = this.dataGrid.getItem(row);

		//this.clearHighlightFeatures();

		if(item) {
			var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
			var f = ol_layer.getFeatureById(item.id);
			var clone = f.clone();

			clone.style =  {
				strokeColor: 'red',
				strokeOpacity: 1.0,
				fillColor: 'red',
				fillOpacity: 0.8
			};

			this.highlightFeatures.push(clone);

			ol_layer.addFeatures([clone], {silent: true});

			//ol_layer.redraw();
		}
	},

	/** Clear all 'highlighting' when the mouse leaves the grid.
	 */
	mouseOut: function(e) {
		this.clearHighlightFeatures();
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		ol_layer.redraw();
	},

	/** Style a selected feature.
	 *
	 */
	styleSelectedFeature: function(f) {
		f.style = {
			strokeColor: 'blue',
			strokeOpacity: .75,
			fillColor: 'blue',
			fillOpacity: .5
		};
	},

	/** Find the selected features and style
	 *  them to show what is "checked".
	 */
	renderSelected: function() {
		// get the OpenLayers layer class.
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;
		// get the list of selected "items"
		var items = this.dataGrid.selection.getSelected();
		for(var i = 0, len = items.length; i < len; i++) {
			if(items[i]) {
				var item = items[i];
				var f = ol_layer.getFeatureById(item.id);
				if(f) {
					this.styleSelectedFeature(f);
					ol_layer.drawFeature(f);
				}
			}
		}
	},

	/** Delayed update when lists are changed.
	 */
	updateGrid: function() {
		var now = (new Date()).getTime();
		// check to see if an update is appropriate
		if(now - this.lastUpdate > this.updateInterval) {
			// clear out the repeating inveral
			clearInterval(this.interval);
			this.interval = null;

			// get the count
			var n_elements = 0;
			function showCount(size, req) {
				n_elements = size;
			}
			this.objectStore.fetch({query: {}, onBegin: showCount, start: 0, count: 0});

			if(n_elements == 0) {
				// clear out the data grid if it exists.
				if(this.layout) {
					var middle = dijit.byId('middle');
					this.layout.removeChild(this.dataGrid);
					this.dataGrid = null;

					middle.removeChild(this.layout);
				}
			} else {
				if(this.dataGrid == null) {
					var middle = dijit.byId('middle');

					if(!this.layout) {
						var dg_layout = new dijit.layout.BorderContainer({
							region: 'bottom', gutters: false,
							splitter: true, liveSplitters: false,
							style: "height: 30%" //125px"
						});

						this.layout = dg_layout;
						middle.addChild(this.layout);

						this.renderToolbar(this.layout);
					}

					this.dataGrid = new dojox.grid.DataGrid({
						region: 'center',
						store: this.objectStore,
						query: {id: '*' },
						structure: this.gridStructure
					});

					dojo.connect(this.dataGrid, 'onMouseOver', this, this.mouseOver);
					dojo.connect(this.dataGrid, 'onMouseOut', this, this.mouseOut);
					dojo.connect(this.dataGrid, 'onSelectionChanged', this, this.renderSelected);

					this.layout.addChild(this.dataGrid);
					this.dataGrid.startup();
					middle.resize();
				}

				this.dataGrid.setQuery({id: '*'});
			}
		}
	},

	triggerUpdate: function() {
		this.lastUpdate = (new Date()).getTime();
		if(this.interval == null) {
			this.interval = setInterval(dojo.hitch(this, this.updateGrid), 400);
		}
	},

	startup: function() {
		// NOTE: This is a bit hacky, whenever possible directly interacting
		//       with an OpenLayers class should be avoided.  That said, I needed
		//       to access the events object.
		var ol_layer = Application.getMapSource(this.conf.targetLayer)._ol_layer;

		this.memoryStore = new dojo.store.Memory({data: []});
		this.objectStore = new dojo.data.ObjectStore({objectStore: this.memoryStore});

		// this will bridge the gap between the openlayers events/features
		//  and the Dojo dataStore object.
		ol_layer.events.register('featureadded', this, function(evt) {
			var obj = evt.feature.attributes;
			// bridge the ID from OpenLayers
			obj['id'] = evt.feature.id;
			this.memoryStore.add(obj, {});

			if(this.gridStructure == null) {
				var view = [
					new dojox.grid.cells.RowIndex({ width: "20px" })
				];

				for(var attr in evt.feature.attributes) {
					// ACHTUNG! Skip the internal identifier.
					//  This could potentially be a bug where the
					//  features have an "id" column.
					if(attr != 'id') {
						view.push({
							name: attr, field: attr, width: 8
						});
					}
				}

				var structure = [{
					type: "dojox.grid._CheckBoxSelector"
				}, view];

				this.gridStructure = structure;

				// remove the old 'grid' and null it out
				//  so that it will be redrawn.
				if(this.dataGrid != null) {
					this.layout.removeChild(this.dataGrid);
					this.dataGrid = null;
				}

			}
			this.triggerUpdate();
		});

		ol_layer.events.register('featureremoved', this, function(evt) {
			this.memoryStore.remove(evt.feature.id);
			this.triggerUpdate();

			// when the feature are cleared out, reset the grid structure.
			if(this.memoryStore.query(function() { return true; }).total == 0) {
				this.gridStructure = null;
			}

		});

	}
});

<|endoftext|>


<commit_before>'use strict';

function objectToString(o) {
  return Object.prototype.toString.call(o);
}

// shim for Node's 'util' package
// DO NOT REMOVE THIS! It is required for compatibility with EnderJS (http://enderjs.com/).
var util = {
  isArray: function (ar) {
    return Array.isArray(ar) || (typeof ar === 'object' && objectToString(ar) === '[object Array]');
  },
  isDate: function (d) {
    return typeof d === 'object' && objectToString(d) === '[object Date]';
  },
  isRegExp: function (re) {
    return typeof re === 'object' && objectToString(re) === '[object RegExp]';
  },
  getRegExpFlags: function (re) {
    var flags = '';
    re.global && (flags += 'g');
    re.ignoreCase && (flags += 'i');
    re.multiline && (flags += 'm');
    return flags;
  }
};

if (typeof module === 'object')
  module.exports = clone;

/**
 * Clones (copies) an Object using deep copying.
 *
 * This function supports circular references by default, but if you are certain
 * there are no circular references in your object, you can save some CPU time
 * by calling clone(obj, false).
 *
 * Caution: if `circular` is false and `parent` contains circular references,
 * your program may enter an infinite loop and crash.
 *
 * @param `parent` - the object to be cloned
 * @param `circular` - set to true if the object to be cloned may contain
 *    circular references. (optional - true by default)
 * @param `depth` - set to a number if the object is only to be cloned to
 *    a particular depth. (optional - defaults to Infinity)
*/

function clone(parent, circular, depth) {
  // maintain two arrays for circular references, where corresponding parents
  // and children have the same index
  var allParents = [];
  var allChildren = [];

  var useBuffer = typeof Buffer != 'undefined';

  if (typeof circular == 'undefined')
    circular = true;

  if (typeof depth == 'undefined')
    depth = Infinity;

  // recurse this function so we don't reset allParents and allChildren
  function _clone(parent, depth) {
    // cloning null always returns null
    if (parent === null)
      return null;

    if (depth == 0)
      return parent;

    var child;
    if (typeof parent != 'object') {
      return parent;
    }

    if (util.isArray(parent)) {
      child = [];
    } else if (util.isRegExp(parent)) {
      child = new RegExp(parent.source, util.getRegExpFlags(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (util.isDate(parent)) {
      child = new Date(parent.getTime());
    } else if (useBuffer && Buffer.isBuffer(parent)) {
      child = new Buffer(parent.length);
      parent.copy(child);
    } else {
      child = Object.create(Object.getPrototypeOf(parent));
    }

    if (circular) {
      var index = allParents.indexOf(parent);

      if (index != -1) {
        return allChildren[index];
      }
      allParents.push(parent);
      allChildren.push(child);
    }

    for (var i in parent) {
      child[i] = _clone(parent[i], depth - 1);
    }

    return child;
  }

  return _clone(parent, depth);
}

/**
 * Simple flat clone using prototype, accepts only objects, usefull for property
 * override on FLAT configuration object (no nested props).
 *
 * USE WITH CAUTION! This may not behave as you wish if you do not know how this
 * works.
 */
clone.clonePrototype = function(parent) {
  if (parent === null)
    return null;

  var c = function () {};
  c.prototype = parent;
  return new c();
};
<commit_msg>allow the prototype to be specified when calling clone.<commit_after>'use strict';

function objectToString(o) {
  return Object.prototype.toString.call(o);
}

// shim for Node's 'util' package
// DO NOT REMOVE THIS! It is required for compatibility with EnderJS (http://enderjs.com/).
var util = {
  isArray: function (ar) {
    return Array.isArray(ar) || (typeof ar === 'object' && objectToString(ar) === '[object Array]');
  },
  isDate: function (d) {
    return typeof d === 'object' && objectToString(d) === '[object Date]';
  },
  isRegExp: function (re) {
    return typeof re === 'object' && objectToString(re) === '[object RegExp]';
  },
  getRegExpFlags: function (re) {
    var flags = '';
    re.global && (flags += 'g');
    re.ignoreCase && (flags += 'i');
    re.multiline && (flags += 'm');
    return flags;
  }
};

if (typeof module === 'object')
  module.exports = clone;

/**
 * Clones (copies) an Object using deep copying.
 *
 * This function supports circular references by default, but if you are certain
 * there are no circular references in your object, you can save some CPU time
 * by calling clone(obj, false).
 *
 * Caution: if `circular` is false and `parent` contains circular references,
 * your program may enter an infinite loop and crash.
 *
 * @param `parent` - the object to be cloned
 * @param `circular` - set to true if the object to be cloned may contain
 *    circular references. (optional - true by default)
 * @param `depth` - set to a number if the object is only to be cloned to
 *    a particular depth. (optional - defaults to Infinity)
*/

function clone(parent, circular, depth, prototype) {
  // maintain two arrays for circular references, where corresponding parents
  // and children have the same index
  var allParents = [];
  var allChildren = [];

  var useBuffer = typeof Buffer != 'undefined';

  if (typeof circular == 'undefined')
    circular = true;

  if (typeof depth == 'undefined')
    depth = Infinity;

  // recurse this function so we don't reset allParents and allChildren
  function _clone(parent, depth) {
    // cloning null always returns null
    if (parent === null)
      return null;

    if (depth == 0)
      return parent;

    var child;
    if (typeof parent != 'object') {
      return parent;
    }

    if (util.isArray(parent)) {
      child = [];
    } else if (util.isRegExp(parent)) {
      child = new RegExp(parent.source, util.getRegExpFlags(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (util.isDate(parent)) {
      child = new Date(parent.getTime());
    } else if (useBuffer && Buffer.isBuffer(parent)) {
      child = new Buffer(parent.length);
      parent.copy(child);
    } else {
      if (prototype) child = Object.create(prototype);
      else child = Object.create(Object.getPrototypeOf(parent));
    }

    if (circular) {
      var index = allParents.indexOf(parent);

      if (index != -1) {
        return allChildren[index];
      }
      allParents.push(parent);
      allChildren.push(child);
    }

    for (var i in parent) {
      child[i] = _clone(parent[i], depth - 1);
    }

    return child;
  }

  return _clone(parent, depth);
}

/**
 * Simple flat clone using prototype, accepts only objects, usefull for property
 * override on FLAT configuration object (no nested props).
 *
 * USE WITH CAUTION! This may not behave as you wish if you do not know how this
 * works.
 */
clone.clonePrototype = function(parent) {
  if (parent === null)
    return null;

  var c = function () {};
  c.prototype = parent;
  return new c();
};
<|endoftext|>


<commit_before>'use strict';

/*
 * The purpose of this module for executing Saved Scheduled Queries (SSQ)
 * to determine whether to set a notification.
 */

module.exports = function(logger, client, userIndex, userType, dataIndex, dateField, sendAlertEmailCallback) {

  var createSavedQueryBody = function(savedQuery) {
    var savedQueryBody = {
      query: JSON.parse(savedQuery.esState.query),
      size: 1,
      sort: {}
    };
    if(savedQuery.filter) {
      savedQuery.filter = JSON.parse(savedQuery.esState.filter);
    }
    savedQueryBody.sort[dateField] = {
      order: 'desc'
    };
    return savedQueryBody;
  };

  var checkAndSaveAlert = function(savedQuery, results) {
    if(results.hits.hits.length) {
      // Check the date of the newest item and compare it with the date on which the saved query was last run.
      var resultDate = new Date(results.hits.hits[0]._source[dateField]);
      var lastDate = new Date(savedQuery.lastRunDate);
      if(resultDate.getTime() > lastDate.getTime()) {
        // Update the notification date so the app knows to show an alert.
        savedQuery.notificationHasRun = false;
        savedQuery.notificationDate = new Date();
      }
    }
    return savedQuery;
  };

  var updateUser = function(user, savedQueries, callback) {
    // Update the saved queries for the user in the database.
    client.update({
      index: userIndex,
      type: userType,
      id: user._id,
      body: {
        doc: {
          savedQueries: savedQueries
        }
      }
    }, function(error, response) {
      if(error) {
        logger.error(error, 'Error updating user ' + user._id);
      }
      callback(savedQueries);
    });
  };

  var checkNextQuery = function(savedQueries, updatedQueries, callback) {
    if(!savedQueries.length) {
      callback(updatedQueries);
      return;
    }

    var done = function(savedQuery) {
      // Add the saved query to the updated query list.  Then check the next saved query.
      updatedQueries.push(savedQuery);
      checkNextQuery(savedQueries.slice(1), updatedQueries, callback);
    };

    var savedQuery = savedQueries[0];

    // Check if we need to run the saved query.
    if(savedQuery.sendEmailNotification) {
      client.search({
        index: dataIndex,
        type: ['webpage'],
        body: createSavedQueryBody(savedQuery)
      }).then(function(results) {
        // Check the results of the saved query for new items.
        done(checkAndSaveAlert(savedQuery, results));
      }, function(error) {
        logger.error(error, 'Error running saved query');
        done(savedQuery);
      });
    } else {
      done(savedQuery);
    }
  };

  var sendAlertEmailIfNeeded = function(user, updatedQueries, callback) {
    var savedQueryNames = updatedQueries.filter(function(query) {
      return query.sendEmailNotification && !query.notificationHasRun;
    }).map(function(query) {
      return query.name;
    });
    if(sendAlertEmailCallback && user._source.emailAddress && savedQueryNames.length) {
      sendAlertEmailCallback(user._source.emailAddress, savedQueryNames, function(error, response) {
        if(error) {
          logger.error(error);
        }
        callback();
      });
    } else {
      callback();
    }
  };

  var checkAndSaveNextUser = function(users, period) {
    if(!users.length) {
      return;
    }

    var done = function(updatedQueries) {
      // Send an alert email to the user if needed.  Then check the saved queries for the next user.
      sendAlertEmailIfNeeded(users[0], updatedQueries || [], function() {
        checkAndSaveNextUser(users.slice(1), period);
      });
    };

    var user = users[0];

    // Check if we need to run the saved queries for the user.
    if(user._source.notificationFrequency === period) {
      checkNextQuery(user._source.savedQueries, [], function(updatedQueries) {
        updateUser(user, updatedQueries, done);
      });
    } else {
      done();
    }
  };

  var checkUsers = function(period) {
    return function() {
      // Get the list of all users.
      client.search({
        index: userIndex,
        type: userType,
        body: {}
      }).then(function(users) {
        checkAndSaveNextUser(users.hits.hits, period);
      }, function(error) {
        logger.error(error, 'Error getting users');
      });
    };
  };

  return {
    checkUsersDaily: checkUsers('daily'),
    checkUsersWeekly: checkUsers('weekly')
  };
};
<commit_msg>Fix to prevent sending notification emails when notificationHasRun has never been set.<commit_after>'use strict';

/*
 * The purpose of this module for executing Saved Scheduled Queries (SSQ)
 * to determine whether to set a notification.
 */

module.exports = function(logger, client, userIndex, userType, dataIndex, dateField, sendAlertEmailCallback) {

  var createSavedQueryBody = function(savedQuery) {
    var savedQueryBody = {
      query: JSON.parse(savedQuery.esState.query),
      size: 1,
      sort: {}
    };
    if(savedQuery.filter) {
      savedQuery.filter = JSON.parse(savedQuery.esState.filter);
    }
    savedQueryBody.sort[dateField] = {
      order: 'desc'
    };
    return savedQueryBody;
  };

  var checkAndSaveAlert = function(savedQuery, results) {
    if(results.hits.hits.length) {
      // Check the date of the newest item and compare it with the date on which the saved query was last run.
      var resultDate = new Date(results.hits.hits[0]._source[dateField]);
      var lastDate = new Date(savedQuery.lastRunDate);
      if(resultDate.getTime() > lastDate.getTime()) {
        // Update the notification date so the app knows to show an alert.
        savedQuery.notificationHasRun = false;
        savedQuery.notificationDate = new Date();
      }
    }
    return savedQuery;
  };

  var updateUser = function(user, savedQueries, callback) {
    // Update the saved queries for the user in the database.
    client.update({
      index: userIndex,
      type: userType,
      id: user._id,
      body: {
        doc: {
          savedQueries: savedQueries
        }
      }
    }, function(error, response) {
      if(error) {
        logger.error(error, 'Error updating user ' + user._id);
      }
      callback(savedQueries);
    });
  };

  var checkNextQuery = function(savedQueries, updatedQueries, callback) {
    if(!savedQueries.length) {
      callback(updatedQueries);
      return;
    }

    var done = function(savedQuery) {
      // Add the saved query to the updated query list.  Then check the next saved query.
      updatedQueries.push(savedQuery);
      checkNextQuery(savedQueries.slice(1), updatedQueries, callback);
    };

    var savedQuery = savedQueries[0];

    // Check if we need to run the saved query.
    if(savedQuery.sendEmailNotification) {
      client.search({
        index: dataIndex,
        type: ['webpage'],
        body: createSavedQueryBody(savedQuery)
      }).then(function(results) {
        // Check the results of the saved query for new items.
        done(checkAndSaveAlert(savedQuery, results));
      }, function(error) {
        logger.error(error, 'Error running saved query');
        done(savedQuery);
      });
    } else {
      done(savedQuery);
    }
  };

  var sendAlertEmailIfNeeded = function(user, updatedQueries, callback) {
    var savedQueryNames = updatedQueries.filter(function(query) {
      return query.sendEmailNotification && (query.notificationHasRun === false);
    }).map(function(query) {
      return query.name;
    });
    if(sendAlertEmailCallback && user._source.emailAddress && savedQueryNames.length) {
      sendAlertEmailCallback(user._source.emailAddress, savedQueryNames, function(error, response) {
        if(error) {
          logger.error(error);
        }
        callback();
      });
    } else {
      callback();
    }
  };

  var checkAndSaveNextUser = function(users, period) {
    if(!users.length) {
      return;
    }

    var done = function(updatedQueries) {
      // Send an alert email to the user if needed.  Then check the saved queries for the next user.
      sendAlertEmailIfNeeded(users[0], updatedQueries || [], function() {
        checkAndSaveNextUser(users.slice(1), period);
      });
    };

    var user = users[0];

    // Check if we need to run the saved queries for the user.
    if(user._source.notificationFrequency === period) {
      checkNextQuery(user._source.savedQueries, [], function(updatedQueries) {
        updateUser(user, updatedQueries, done);
      });
    } else {
      done();
    }
  };

  var checkUsers = function(period) {
    return function() {
      // Get the list of all users.
      client.search({
        index: userIndex,
        type: userType,
        body: {}
      }).then(function(users) {
        checkAndSaveNextUser(users.hits.hits, period);
      }, function(error) {
        logger.error(error, 'Error getting users');
      });
    };
  };

  return {
    checkUsersDaily: checkUsers('daily'),
    checkUsersWeekly: checkUsers('weekly')
  };
};
<|endoftext|>