<commit_before>import { app, BrowserWindow } from 'electron'

let mainWindow = null

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('ready', () => {
    // Initialize the window to our specified dimensions
    mainWindow = new BrowserWindow({
        width: 1024,
        height: 768,
        icon: `${__dirname}/static/pi_logo.png`,
        frame: true
    })

    // Tell Electron where to load the entry point from
    mainWindow.loadURL(`file://${__dirname}/app.html`)

    // Clear out the main window when the app is closed
    mainWindow.on('closed', () => {
        mainWindow = null
    })
})
<commit_msg>adds dev tools to the app<commit_after>import { app, BrowserWindow } from 'electron'

let mainWindow = null

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('ready', () => {
    // Initialize the window to our specified dimensions
    mainWindow = new BrowserWindow({
        width: 1024,
        height: 768,
        icon: `${__dirname}/static/pi_logo.png`,
        frame: true
    })
    mainWindow.openDevTools()

    // Tell Electron where to load the entry point from
    mainWindow.loadURL(`file://${__dirname}/app.html`)

    // Clear out the main window when the app is closed
    mainWindow.on('closed', () => {
        mainWindow = null
    })
})
<|endoftext|>


<commit_before>const Problem = require('../models/Problem')
const { extractPagination, isUndefined } = require('../utils')

/** 返回题目列表 */
async function queryList (ctx, next) {
  const filter = {} // 用于 mongoose 的筛选

  if (ctx.query.field) {
    // https://docs.mongodb.com/manual/reference/operator/query/where/
    // field 一般为 cid 或 title
    // ctx.query.query 表示搜获的内容
    // 使用 string 而不是 function 是因为，这里的 function 是不能访问到外部变量的
    // 用模板字符串生成筛选条件
    // 正则可以匹配更多的内容
    filter.$where =
      `${new RegExp(ctx.query.query, 'i')}.test(this["${ctx.query.field}"])`
  }

  const res = await Problem
    .paginate(filter, {
      limit: +ctx.query.limit || 30, // 加号表示使其变为数字
      page: +ctx.query.page || 1,
      sort: {pid: 1},
      // '-_id' 结果不包含 _id
      // http://stackoverflow.com/questions/9598505/mongoose-retrieving-data-without-id-field
      select: '-_id title pid solve submit status'
    })

  ctx.body = {
    problems: res.docs,
    pagination: extractPagination(res)
  }
}

/** 指定pid, 返回一个具体的题目 */
async function queryOneProblem (ctx, next) {
  const pid = +ctx.params.pid
  if (isNaN(pid)) {
    ctx.throw(400, 'Pid should be a number')
  }

  const problem = await Problem
    .findOne({pid})
    .select('-_id pid title memory time description input output in out hint')
    .exec()

  if (!problem) {
    ctx.throw(400, 'No such a problem')
  }

  ctx.body = {
    problem
  }
}

/** 指定 pid, 更新一道已经存在的题目 */
async function update (ctx, next) {
  const pid = +ctx.params.pid
  if (isNaN(pid)) {
    ctx.throw(400, 'Pid should be a number')
  }

  const problem = await Problem
    .findOne({pid})
    .exec()

  if (!problem) {
    ctx.throw(400, 'No such a problem')
  }

  const verified = Problem.validate(ctx.request.body)
  if (!verified.valid) {
    ctx.throw(400, verified.error)
  }

  // 可更新的字段
  const fields = ['title', 'time', 'memory', 'input', 'output', 'in', 'out',
    'description', 'hint']

  fields.forEach((field) => {
    if (!isUndefined(ctx.request.body[field])) {
      problem[field] = ctx.request.body[field]
    }
  })

  await problem.save()

  ctx.body = {
    problem: {
      pid,
      title: problem.title
    }
  }
}

module.exports = {
  queryList,
  queryOneProblem,
  update
}
<commit_msg>add a modifiable field<commit_after>const Problem = require('../models/Problem')
const { extractPagination, isUndefined } = require('../utils')

/** 返回题目列表 */
async function queryList (ctx, next) {
  const filter = {} // 用于 mongoose 的筛选

  if (ctx.query.field) {
    // https://docs.mongodb.com/manual/reference/operator/query/where/
    // field 一般为 cid 或 title
    // ctx.query.query 表示搜获的内容
    // 使用 string 而不是 function 是因为，这里的 function 是不能访问到外部变量的
    // 用模板字符串生成筛选条件
    // 正则可以匹配更多的内容
    filter.$where =
      `${new RegExp(ctx.query.query, 'i')}.test(this["${ctx.query.field}"])`
  }

  const res = await Problem
    .paginate(filter, {
      limit: +ctx.query.limit || 30, // 加号表示使其变为数字
      page: +ctx.query.page || 1,
      sort: {pid: 1},
      // '-_id' 结果不包含 _id
      // http://stackoverflow.com/questions/9598505/mongoose-retrieving-data-without-id-field
      select: '-_id title pid solve submit status'
    })

  ctx.body = {
    problems: res.docs,
    pagination: extractPagination(res)
  }
}

/** 指定pid, 返回一个具体的题目 */
async function queryOneProblem (ctx, next) {
  const pid = +ctx.params.pid
  if (isNaN(pid)) {
    ctx.throw(400, 'Pid should be a number')
  }

  const problem = await Problem
    .findOne({pid})
    .select('-_id pid title memory time description input output in out hint')
    .exec()

  if (!problem) {
    ctx.throw(400, 'No such a problem')
  }

  ctx.body = {
    problem
  }
}

/** 指定 pid, 更新一道已经存在的题目 */
async function update (ctx, next) {
  const pid = +ctx.params.pid
  if (isNaN(pid)) {
    ctx.throw(400, 'Pid should be a number')
  }

  const problem = await Problem
    .findOne({pid})
    .exec()

  if (!problem) {
    ctx.throw(400, 'No such a problem')
  }

  const verified = Problem.validate(ctx.request.body)
  if (!verified.valid) {
    ctx.throw(400, verified.error)
  }

  // 可更新的字段
  const fields = ['title', 'time', 'memory', 'input', 'output', 'in', 'out',
    'description', 'hint', 'status']

  fields.forEach((field) => {
    if (!isUndefined(ctx.request.body[field])) {
      problem[field] = ctx.request.body[field]
    }
  })

  await problem.save()

  ctx.body = {
    problem: {
      pid,
      title: problem.title
    }
  }
}

module.exports = {
  queryList,
  queryOneProblem,
  update
}
<|endoftext|>


<commit_before>wp.customize.controlConstructor['kirki-date'] = wp.customize.kirkiDynamicControl.extend({

	initKirkiControl: function() {

		var control  = this,
		    selector = control.selector + ' input.datepicker';

		// Only add in WP 4.9+.
		if ( _.isUndefined( wp.customize.DateTimeControl ) ) {
			return;
		}

		// New method for the DateTime control.
		wp.customize.control.add( new wp.customize.DateTimeControl( control.id, {
			section: control.params.section,
			priority: control.params.priority,
			label: control.params.label,
			description: control.params.description,
			setting: control.id,
			'default': control.params['default']
		} ) );
	}
});
<commit_msg>bugfix for datetime control<commit_after>wp.customize.controlConstructor['kirki-date'] = wp.customize.kirkiDynamicControl.extend({

	initKirkiControl: function() {

		var control  = this,
		    selector = control.selector + ' input.datepicker';

		// Only add in WP 4.9+.
		if ( _.isUndefined( wp.customize.DateTimeControl ) ) {
			return;
		}

		// New method for the DateTime control.
		wp.customize.control.add( new wp.customize.DateTimeControl( control.id, {
			section: control.params.section,
			priority: control.params.priority,
			label: control.params.label,
			description: control.params.description,
			settings: { 'default': control.id },
			'default': control.params['default']
		} ) );
	}
});
<|endoftext|>


<commit_before>module.exports = (message, client, helper) => {
    const split_content = message.content.split(" ").slice(1);
    const command = split_content[0];
    if (command == "start") {
        if (message.gcfg.trivia == message.channel.id) {
            client.trivia.init(client, message.channel.id);
            helper.log(message, "trivia started");
        } else if (message.gcfg.trivia != 0 && message.gcfg.trivia != message.channel.id) {
            message.channel.createMessage(`Try this command in the trivia channel! <#${message.gcfg.trivia}>`);
        } else {
            message.channel.createMessage(`This server does not have a designated trivia channel! Try \`${message.gcfg.prefix}help admin trivia\`.`);
        }
        return;
    }

    if (command == "stop") {
        // if (!message.member.permission.has("manageMessages")) break;
        if (client.trivia.channels.includes(message.gcfg.trivia)) client.trivia.channels.splice(client.trivia.channels.indexOf(message.gcfg.trivia), 1);
        message.channel.createMessage(":ok_hand: Trivia stopped.").catch(err => helper.handle(message, err));
        helper.log(message, "trivia stopped manually");
        return;
    }

    if (["top", "leaderboard", "scores"].includes(command)) {
        const subcommand = split_content[1];

        client.pg.query("SELECT * FROM scores ORDER BY score DESC;").then(res => {
            let rows = res.rows;
            let msg = [];
            if (subcommand == "all") {
                msg.push("Top 10 users throughout bot: ");
            } else {
                rows = rows.filter(row => message.channel.guild.members.get(row.id));
                msg.push("Top 10 users in this server: ");
            }
            rows = rows.slice(0, 10);
            msg.push(...rows.map(row => `${client.users.find(user => user.id == row.id).username}: ${row.score}`));
            message.channel.createMessage(msg.join("\n"))
                .then(() => helper.log(message, `sent trivia leaderboard ${subcommand == "all" ? "of everywhere" : "of guild"}`))
                .catch(err => helper.handle(message, err));
        }).catch(err => {
            helper.log(message, "something went wrong selecting scores");
            helper.log(message, err);
        });
        return;
    }

    if (command == "stats") {
        client.pg.query("SELECT * FROM scores ORDER BY score DESC;").then(res => {
            let embed = {
                "description": [
                    `**Current trivia games running:** ${client.trivia.channels.length}`,
                    `**Total questions:** ${client.trivia.questions.length}`,
                    `**First place:** ${client.users.find(user => user.id == res.rows[0].id).username} - ${res.rows[0].score}`
                ].join("\n"),
                "timestamp": new Date()
            };

            message.channel.createMessage({
                "embed": embed
            }).catch(err => helper.handle(message, err)).then(() => {
                helper.log(message, "sent trivia stats");
            });
        });
        return;
    }

    if (command == "points") {
        client.pg.query("SELECT * FROM scores ORDER BY score DESC;").then(res => {
            let search = client.core.util.search_members(message.channel.guild.members, split_content.slice(1));
            search = search[Object.keys(search)[0]];
            let found = search || message.author.id;
            let data = res.rows.find(row => row.id == found);
            let guild = res.rows.filter(row => message.channel.guild.members.get(row.id));
            if (!data) {
                message.channel.createMessage(`${client.users.get(found).username} hasn't played trivia yet!`).catch(err => helper.handle(message, err));
                return;
            }

            let embed = {
                "author": {
                    "name": client.users.get(found).username,
                    "icon_url": client.users.get(found).avatarURL
                },
                "description": [
                    `**Points:** ${data.score}`,
                    `**Highest Streak:** ${data.streak}`,
                    `**Server Rank:** ${guild.indexOf(data) + 1}/${guild.length}`,
                    `**Global Rank:** ${res.rows.indexOf(data) + 1}/${res.rows.length}`
                ].join("\n"),
                "timestamp": new Date()
            };

            message.channel.createMessage({
                "embed": embed
            }).catch(err => helper.handle(message, err)).then(() => {
                helper.log(message, "sent trivia points for a user");
            });
        });
        return;
    }

    message.content = "help trivia";
    client.core.commands.help(message, client, helper);
};
<commit_msg>dont start trivia more than once<commit_after>module.exports = (message, client, helper) => {
    const split_content = message.content.split(" ").slice(1);
    const command = split_content[0];
    if (command == "start") {
        if (message.gcfg.trivia == message.channel.id && !client.trivia.channels.includes(message.channel.id)) {
            client.trivia.init(client, message.channel.id);
            helper.log(message, "trivia started");
        } else if (message.gcfg.trivia != 0 && message.gcfg.trivia != message.channel.id) {
            message.channel.createMessage(`Try this command in the trivia channel! <#${message.gcfg.trivia}>`);
        } else if (message.gcfg.trivia == 0 || message.gcfg.trivia == undefined) {
            message.channel.createMessage(`This server does not have a designated trivia channel! Try \`${message.gcfg.prefix}help admin trivia\`.`);
        }
        return;
    }

    if (command == "stop") {
        // if (!message.member.permission.has("manageMessages")) break;
        if (client.trivia.channels.includes(message.gcfg.trivia)) client.trivia.channels.splice(client.trivia.channels.indexOf(message.gcfg.trivia), 1);
        message.channel.createMessage(":ok_hand: Trivia stopped.").catch(err => helper.handle(message, err));
        helper.log(message, "trivia stopped manually");
        return;
    }

    if (["top", "leaderboard", "scores"].includes(command)) {
        const subcommand = split_content[1];

        client.pg.query("SELECT * FROM scores ORDER BY score DESC;").then(res => {
            let rows = res.rows;
            let msg = [];
            if (subcommand == "all") {
                msg.push("Top 10 users throughout bot: ");
            } else {
                rows = rows.filter(row => message.channel.guild.members.get(row.id));
                msg.push("Top 10 users in this server: ");
            }
            rows = rows.slice(0, 10);
            msg.push(...rows.map(row => `${client.users.find(user => user.id == row.id).username}: ${row.score}`));
            message.channel.createMessage(msg.join("\n"))
                .then(() => helper.log(message, `sent trivia leaderboard ${subcommand == "all" ? "of everywhere" : "of guild"}`))
                .catch(err => helper.handle(message, err));
        }).catch(err => {
            helper.log(message, "something went wrong selecting scores");
            helper.log(message, err);
        });
        return;
    }

    if (command == "stats") {
        client.pg.query("SELECT * FROM scores ORDER BY score DESC;").then(res => {
            let embed = {
                "description": [
                    `**Current trivia games running:** ${client.trivia.channels.length}`,
                    `**Total questions:** ${client.trivia.questions.length}`,
                    `**First place:** ${client.users.find(user => user.id == res.rows[0].id).username} - ${res.rows[0].score}`
                ].join("\n"),
                "timestamp": new Date()
            };

            message.channel.createMessage({
                "embed": embed
            }).catch(err => helper.handle(message, err)).then(() => {
                helper.log(message, "sent trivia stats");
            });
        });
        return;
    }

    if (command == "points") {
        client.pg.query("SELECT * FROM scores ORDER BY score DESC;").then(res => {
            let search = client.core.util.search_members(message.channel.guild.members, split_content.slice(1));
            search = search[Object.keys(search)[0]];
            let found = search || message.author.id;
            let data = res.rows.find(row => row.id == found);
            let guild = res.rows.filter(row => message.channel.guild.members.get(row.id));
            if (!data) {
                message.channel.createMessage(`${client.users.get(found).username} hasn't played trivia yet!`).catch(err => helper.handle(message, err));
                return;
            }

            let embed = {
                "author": {
                    "name": client.users.get(found).username,
                    "icon_url": client.users.get(found).avatarURL
                },
                "description": [
                    `**Points:** ${data.score}`,
                    `**Highest Streak:** ${data.streak}`,
                    `**Server Rank:** ${guild.indexOf(data) + 1}/${guild.length}`,
                    `**Global Rank:** ${res.rows.indexOf(data) + 1}/${res.rows.length}`
                ].join("\n"),
                "timestamp": new Date()
            };

            message.channel.createMessage({
                "embed": embed
            }).catch(err => helper.handle(message, err)).then(() => {
                helper.log(message, "sent trivia points for a user");
            });
        });
        return;
    }

    message.content = "help trivia";
    client.core.commands.help(message, client, helper);
};
<|endoftext|>


<commit_before>(function () {
'use strict';
define(['util', 'underscore', 'jquery-ui'], function (Util, _) {
	var PrivateFunctions = {
		switchLexer: function (lexer, baseUrl) {
			var url = baseUrl + '/' + Util.fixedEncodeURIComponent(lexer);
			window.location = url;
		},
		lexerSelected: function (event, ui) {
			event.preventDefault();
			var baseUrl = $(event.target).data('base-url');
			this.switchLexer(ui.item.value, baseUrl);
		},
		setupAutocomplete: function (lexers) {
			var lexerSource = [];
			for (var key in lexers) {
				lexerSource.push({ label: lexers[key], value: key });
			}

			$('.lexer-form input').autocomplete({
				source: lexerSource,
				select: _.bind(PrivateFunctions.lexerSelected, PrivateFunctions)
			});
		},
		setupEvents: function () {
			$('.lexer-form').on('submit', _.bind(function (event) {
				event.preventDefault();
				var input = $(event.target).find('input');
				var lexer = input.val();
				var baseUrl = input.data('base-url');
				this.switchLexer(lexer, baseUrl);
			}, this));

			$('.lexer-toggle').on('click', function(event) {
				setTimeout(function() {
					var dropDown = $(event.target).siblings('.dropdown-menu');
					dropDown.find('input').focus();
				}, 0);
			});
		}
	};
	var LexerInput = {
		initialize: function (lexers) {
			PrivateFunctions.setupAutocomplete(lexers);
			PrivateFunctions.setupEvents();
		}
	};

	return LexerInput;
});
})();
<commit_msg>Use util function for focusing dropdown menu input<commit_after>(function () {
'use strict';
define(['util', 'underscore', 'jquery-ui'], function (Util, _) {
	var PrivateFunctions = {
		switchLexer: function (lexer, baseUrl) {
			var url = baseUrl + '/' + Util.fixedEncodeURIComponent(lexer);
			window.location = url;
		},
		lexerSelected: function (event, ui) {
			event.preventDefault();
			var baseUrl = $(event.target).data('base-url');
			this.switchLexer(ui.item.value, baseUrl);
		},
		setupAutocomplete: function (lexers) {
			var lexerSource = [];
			for (var key in lexers) {
				lexerSource.push({ label: lexers[key], value: key });
			}

			$('.lexer-form input').autocomplete({
				source: lexerSource,
				select: _.bind(PrivateFunctions.lexerSelected, PrivateFunctions)
			});
		},
		setupEvents: function () {
			$('.lexer-form').on('submit', _.bind(function (event) {
				event.preventDefault();
				var input = $(event.target).find('input');
				var lexer = input.val();
				var baseUrl = input.data('base-url');
				this.switchLexer(lexer, baseUrl);
			}, this));

			$('.lexer-toggle').on('click', _.bind(function(event) {
				Util.focusDropdownInput(event.target);
			}, Util));
		}
	};
	var LexerInput = {
		initialize: function (lexers) {
			PrivateFunctions.setupAutocomplete(lexers);
			PrivateFunctions.setupEvents();
		}
	};

	return LexerInput;
});
})();
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

const async = require('async');
const debug = require('debug')('engine_util');
const traverse = require('traverse');
const esprima = require('esprima');
const L = require('lodash');
const vm = require('vm');
const A = require('async');
const jsonpath = require('jsonpath');
const cheerio = require('cheerio');
const jitter = require('./jitter').jitter;

let xmlCapture;
try {
  xmlCapture = require('artillery-xml-capture');
} catch (e) {
  xmlCapture = null;
}

module.exports = {
  createThink: createThink,
  createLoopWithCount: createLoopWithCount,
  createParallel: createParallel,
  isProbableEnough: isProbableEnough,
  template: template,
  captureOrMatch,
  evil: evil,
  _renderVariables: renderVariables
};

function createThink(requestSpec, opts) {
  opts = opts || {};

  let thinkspec = requestSpec.think;

  let f = function think(context, callback) {
    let thinktime = parseFloat(template(thinkspec, context)) * 1000;
    if (requestSpec.jitter || opts.jitter) {
      thinktime = jitter(`${thinktime}:${requestSpec.jitter || opts.jitter}`);
    }
    debug('think %s, %s, %s -> %s', requestSpec.think, requestSpec.jitter, opts.jitter, thinktime);
    setTimeout(function() {
      callback(null, context);
    }, thinktime);
  };

  return f;
}

// "count" can be an integer (negative or positive) or a string defining a range
// like "1-15"
function createLoopWithCount(count, steps, opts) {
  return function aLoop(context, callback) {

    let count2 = count;
    if (typeof count === 'string') {
      count2 = template(count, context);
    }

    let from = parseLoopCount(count2).from;
    let to = parseLoopCount(count2).to;

    let i = from;
    let newContext = context;
    let loopIndexVar = (opts && opts.loopValue) || '$loopCount';
    let loopElementVar = (opts && opts.loopElement) || '$loopElement';
    // Should we stop early because the value of "over" is not an array
    let abortEarly = false;

    let overValues = null;
    let loopValue = null;
    if (typeof opts.overValues !== 'undefined') {
      if (opts.overValues && typeof opts.overValues === 'object') {
        overValues = opts.overValues;
        loopValue = overValues[i];
      } else if (opts.overValues && typeof opts.overValues === 'string') {
        overValues = context.vars[opts.overValues];
        if (L.isArray(overValues)) {
          loopValue = overValues[i];
        } else {
          abortEarly = true;
        }
      }
    }

    newContext.vars[loopElementVar] = loopValue;
    newContext.vars[loopIndexVar] = i;

    let shouldContinue = true;

    A.whilst(
      function test() {
        if (abortEarly) {
          return false;
        }
        if (opts.whileTrue) {
          return shouldContinue;
        }
        if (overValues !== null) {
          return i !== overValues.length;
        } else {
          return i < to || to === -1;
        }
      },
      function repeated(cb) {
        let zero = function(cb2) {
          return cb2(null, newContext);
        };
        let steps2 = L.flatten([zero, steps]);

        A.waterfall(steps2, function(err, context2) {
          if (err) {
            return cb(err, context2);
          }
          i++;
          newContext = context2;

          newContext.vars[loopIndexVar]++;
          if (overValues !== null) {
            newContext.vars[loopElementVar] = overValues[i];
          }

          if (opts.whileTrue) {
            opts.whileTrue(context2, function done(b) {
              shouldContinue = b;
              return cb(err, context2);
            });
          } else {
            return cb(err, context2);
          }
        });
      },
      function(err, finalContext) {
        if (typeof finalContext === 'undefined') {
          // this happens if test() returns false immediately, e.g. with
          // nested loops where one of the inner loops goes over an
          // empty array
          return callback(err, newContext);
        }
        return callback(err, finalContext);
      });
  };
}

function createParallel(steps, opts) {

  let limit = (opts && opts.limitValue) || 100;

  return function aParallel(context, callback) {
    let newContext = context;
    let newCallback = callback;

    // Remap the steps array to pass the context into each step.
    let newSteps = L.map(steps, function(step) {
      return function(callback){
        step(newContext, callback);
      };
    });

    // Run each of the steps in parallel.
    A.parallelLimit(
      newSteps,
      limit,
      function(err, finalContext) {
        // We don't need to do anything with the array of contexts returned from each step at the moment.
        return newCallback(err, newContext);
      });
  };
}

function isProbableEnough(obj) {
  if (typeof obj.probability === 'undefined') {
    return true;
  }

  let probability = Number(obj.probability) || 0;
  if (probability > 100) {
    probability = 100;
  }

  let r = L.random(100);
  return r < probability;
}

function template(o, context) {
  let result;

  if (typeof o === 'undefined') {
    return undefined;
  }

  if (o.constructor === Object) {
    result = traverse(o).map(function(x) {

      if (typeof x === 'string') {
        this.update(template(x, context));
      } else {
        return x;
      }
    });
  } else if (typeof o === 'string') {
    if (!/{{/.test(o)) {
      return o;
    }
    const funcCallRegex = /{{\s*(\$[A-Za-z0-9_]+\s*\(\s*.*\s*\))\s*}}/;
    let match = o.match(funcCallRegex);
    if (match) {
      // This looks like it could be a function call:
      const syntax = esprima.parse(match[1]);
      // TODO: Use a proper schema for what we expect here
      if (syntax.body && syntax.body.length === 1 &&
          syntax.body[0].type === 'ExpressionStatement') {
        let funcName = syntax.body[0].expression.callee.name;
        let args = L.map(syntax.body[0].expression.arguments, function(arg) {
          return arg.value;
        });
        if (funcName in context.funcs) {
          return template(o.replace(funcCallRegex, context.funcs[funcName].apply(null, args)), context);
        }
      }
    } else {
      if (!o.match(/{{/)) {
        return o;
      }

      result = renderVariables(o, context.vars);
    }
  } else {
    return o;
  }

  return result;
}

function renderVariables (str, vars) {
  const RX = /{{{?[\s$\w\.\[\]\'\"]+}}}?/g;
  let rxmatch;
  let result = str.substring(0, str.length);

  // Special case for handling integer/boolean/object substitution:
  //
  // Does the template string contain one variable and nothing else?
  // e.g.: "{{ myvar }" or "{{    myvar }", but NOT " {{ myvar }"
  // If so, we treat it as a special case.
  const matches = str.match(RX);
  if (matches && matches.length === 1) {
    if (matches[0] === str) {
      // there's nothing else in the template but the variable
      const varName = str.replace(/{/g, '').replace(/}/g, '').trim();
      return L.get(vars, varName) || '';
    }
  }

  while (result.search(RX) > -1) {
    let templateStr = result.match(RX)[0];
    const varName = templateStr.replace(/{/g, '').replace(/}/g, '').trim();

    let varValue = L.get(vars, varName);

    if (typeof varValue === 'object') {
      varValue = JSON.stringify(varValue);
    }
    result = result.replace(templateStr, varValue);
  }

  return result;
}

// Presume code is valid JS code (i.e. that it has been checked elsewhere)
function evil(sandbox, code) {
  let context = vm.createContext(sandbox);
  let script = new vm.Script(code);
  try {
    return script.runInContext(context);
  }
  catch(e) {
    return null;
  }
}

function parseLoopCount(countSpec) {
  let from = 0;
  let to = 0;

  if (typeof countSpec === 'number') {
    from = 0;
    to = countSpec;
  } else if (typeof countSpec === 'string') {
    if (isNaN(Number(countSpec))) {
      if (/\d\-\d/.test(countSpec)) {
        from = Number(countSpec.split('-')[0]);
        to = Number(countSpec.split('-')[1]);
      } else {
        to = 0;
      }
    } else {
      to = Number(countSpec);
    }
  } else {
    to = 0;
  }

  return { from: from, to: to };
}

//
// Calls done() with:
// {captures: { var: value }, matches: { var: {expected: '', got: ''} }}
//
function captureOrMatch(params, response, context, done) {
  let result = {
    captures: {},
    matches: {}
  };

  if (!params.capture && !params.match) {
    return done(null, result);
  }

  let specs = L.concat(
    L.get(params, 'capture', []),
    L.get(params, 'match', []));

  async.eachSeries(
    specs,
    function(spec, next) {
      let parsedSpec = parseSpec(spec, response);
      let parser = parsedSpec.parser;
      let extractor = parsedSpec.extractor;
      let expr = parsedSpec.expr;

      // are we looking at body or headers:
      var content = response.body;
      if (spec.header) {
        content = response.headers;
      }

      parser(content, function(err, doc) {
        if (err) {
          // TODO: The message could be improved still
          if (spec.strict) {
            return next(new Error(`Couldn't parse response for '${expr}'`), null);
          }
        }

        let extractedValue = extractor(doc, template(expr, context), spec);

        if (spec.value) {
          // this is a match spec
          let expected = template(spec.value, context);
          debug('match: %s, expected: %s, got: %s', expr, expected, extractedValue);
          if (extractedValue !== expected) {
            result.matches[expr] = {
              success: false,
              expected: expected,
              got: extractedValue,
              expression: expr,
              strict: spec.strict
            };
          } else {
            result.matches.expr = {
              success: true,
              expected: expected,
              expression: expr
            };
          }
          return next(null);
        }

        if (spec.as) {
          // this is a capture
          debug('capture: %s = %s', spec.as, extractedValue);
          result.captures[spec.as] = extractedValue;
          if (spec.transform) {
            let transformedValue = evil(
              result.captures,
              spec.transform);

            debug('transform: %s = %s', spec.as, result.captures[spec.as]);
            result.captures[spec.as] = transformedValue !== null ? transformedValue : extractedValue;
          }
        }

        return next(null);
      });
    },
    function(err) {
      if (err) {
        return done(err, null);
      } else {
        return done(null, result);
      }
    });
}

function parseSpec(spec, response) {
  let parser;
  let extractor;
  let expr;

  if (spec.json) {
    parser = parseJSON;
    extractor = extractJSONPath;
    expr = spec.json;
  } else if (xmlCapture && spec.xpath) {
    parser = xmlCapture.parseXML;
    extractor = xmlCapture.extractXPath;
    expr = spec.xpath;
  } else if (spec.regexp) {
    parser = dummyParser;
    extractor = extractRegExp;
    expr = spec.regexp;
  } else if (spec.header) {
    parser = dummyParser;
    extractor = extractHeader;
    expr = spec.header;
  } else if (spec.selector) {
    parser = dummyParser;
    extractor = extractCheerio;
    expr = spec.selector;
  } else {
    if (isJSON(response)) {
      parser = parseJSON;
      extractor = extractJSONPath;
      expr = spec.json;
    } else if (xmlCapture && isXML(response)) {
      parser = xmlCapture.parseXML;
      extractor = xmlCapture.extractXPath;
      expr = spec.xpath;
    } else {
      // We really don't know what to do here.
      parser = dummyParser;
      extractor = dummyExtractor;
      expr = '';
    }
  }

  return { parser: parser, extractor: extractor, expr: expr };
}

/*
 * Wrap JSON.parse in a callback
 */
function parseJSON(body, callback) {
  let r = null;
  let err = null;

  try {
    if (typeof body === 'string') {
      r = JSON.parse(body);
    } else {
      r = body;
    }
  } catch(e) {
    err = e;
  }

  return callback(err, r);
}

function dummyParser(body, callback) {
  return callback(null, body);
}

// doc is a JSON object
function extractJSONPath(doc, expr) {
  if (typeof doc !== 'object') {
    return '';
  }

  let results;

  try {
    results = jsonpath.query(doc, expr);
  } catch (queryErr) {
    debug(queryErr);
  }

  if (!results) {
    return '';
  }

  if (results.length > 1) {
    return results[randomInt(0, results.length - 1)];
  } else {
    return results[0];
  }
}

// doc is a string or an object (body parsed by Request when headers indicate JSON)
function extractRegExp(doc, expr, opts) {
  let group = opts.group;
  let flags = opts.flags;
  let str;
  if (typeof doc === 'string') {
    str = doc;
  } else {
    str = JSON.stringify(doc); // FIXME: not the same string as the one we got from the server
  }
  let rx;
  if (flags) {
      rx = new RegExp(expr, flags);
  } else {
      rx = new RegExp(expr);
  }
  let match = rx.exec(str);
  if (!match) {
    return '';
  }
  if(group && match[group]) {
    return match[group];
  } else if (match[0]) {
    return match[0];
  } else {
    return '';
  }
}

function extractHeader(headers, headerName) {
  return headers[headerName] || '';
}

function extractCheerio(doc, expr, opts) {
  let $ = cheerio.load(doc);
  let els = $(expr);
  let i = 0;
  if (typeof opts.index !== 'undefined') {
    if (opts.index === 'random') {
      i = Math.ceil(Math.random() * els.get().length - 1);
    } else if (opts.index === 'last') {
      i = els.get().length() - 1;
    } else if (typeof Number(opts.index) === 'number') {
      i = Number(opts.index);
    }
  }
  return els.slice(i, i + 1).attr(opts.attr);
}

function dummyExtractor() {
  return '';
}

/*
 * Given a response object determine if it's JSON
 */
function isJSON(res) {
  debug('isJSON: content-type = %s', res.headers['content-type']);
  return (res.headers['content-type'] &&
          /^application\/json/.test(res.headers['content-type']));
}

/*
 * Given a response object determine if it's some kind of XML
 */
function isXML(res) {
  return (res.headers['content-type'] &&
          (/^[a-zA-Z]+\/xml/.test(res.headers['content-type']) ||
           /^[a-zA-Z]+\/[a-zA-Z]+\+xml/.test(res.headers['content-type'])));

}

function randomInt (low, high) {
  return Math.floor(Math.random() * (high - low + 1) + low);
}
<commit_msg>fix(util): Handle case when the input may be null<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

const async = require('async');
const debug = require('debug')('engine_util');
const traverse = require('traverse');
const esprima = require('esprima');
const L = require('lodash');
const vm = require('vm');
const A = require('async');
const jsonpath = require('jsonpath');
const cheerio = require('cheerio');
const jitter = require('./jitter').jitter;

let xmlCapture;
try {
  xmlCapture = require('artillery-xml-capture');
} catch (e) {
  xmlCapture = null;
}

module.exports = {
  createThink: createThink,
  createLoopWithCount: createLoopWithCount,
  createParallel: createParallel,
  isProbableEnough: isProbableEnough,
  template: template,
  captureOrMatch,
  evil: evil,
  _renderVariables: renderVariables
};

function createThink(requestSpec, opts) {
  opts = opts || {};

  let thinkspec = requestSpec.think;

  let f = function think(context, callback) {
    let thinktime = parseFloat(template(thinkspec, context)) * 1000;
    if (requestSpec.jitter || opts.jitter) {
      thinktime = jitter(`${thinktime}:${requestSpec.jitter || opts.jitter}`);
    }
    debug('think %s, %s, %s -> %s', requestSpec.think, requestSpec.jitter, opts.jitter, thinktime);
    setTimeout(function() {
      callback(null, context);
    }, thinktime);
  };

  return f;
}

// "count" can be an integer (negative or positive) or a string defining a range
// like "1-15"
function createLoopWithCount(count, steps, opts) {
  return function aLoop(context, callback) {

    let count2 = count;
    if (typeof count === 'string') {
      count2 = template(count, context);
    }

    let from = parseLoopCount(count2).from;
    let to = parseLoopCount(count2).to;

    let i = from;
    let newContext = context;
    let loopIndexVar = (opts && opts.loopValue) || '$loopCount';
    let loopElementVar = (opts && opts.loopElement) || '$loopElement';
    // Should we stop early because the value of "over" is not an array
    let abortEarly = false;

    let overValues = null;
    let loopValue = null;
    if (typeof opts.overValues !== 'undefined') {
      if (opts.overValues && typeof opts.overValues === 'object') {
        overValues = opts.overValues;
        loopValue = overValues[i];
      } else if (opts.overValues && typeof opts.overValues === 'string') {
        overValues = context.vars[opts.overValues];
        if (L.isArray(overValues)) {
          loopValue = overValues[i];
        } else {
          abortEarly = true;
        }
      }
    }

    newContext.vars[loopElementVar] = loopValue;
    newContext.vars[loopIndexVar] = i;

    let shouldContinue = true;

    A.whilst(
      function test() {
        if (abortEarly) {
          return false;
        }
        if (opts.whileTrue) {
          return shouldContinue;
        }
        if (overValues !== null) {
          return i !== overValues.length;
        } else {
          return i < to || to === -1;
        }
      },
      function repeated(cb) {
        let zero = function(cb2) {
          return cb2(null, newContext);
        };
        let steps2 = L.flatten([zero, steps]);

        A.waterfall(steps2, function(err, context2) {
          if (err) {
            return cb(err, context2);
          }
          i++;
          newContext = context2;

          newContext.vars[loopIndexVar]++;
          if (overValues !== null) {
            newContext.vars[loopElementVar] = overValues[i];
          }

          if (opts.whileTrue) {
            opts.whileTrue(context2, function done(b) {
              shouldContinue = b;
              return cb(err, context2);
            });
          } else {
            return cb(err, context2);
          }
        });
      },
      function(err, finalContext) {
        if (typeof finalContext === 'undefined') {
          // this happens if test() returns false immediately, e.g. with
          // nested loops where one of the inner loops goes over an
          // empty array
          return callback(err, newContext);
        }
        return callback(err, finalContext);
      });
  };
}

function createParallel(steps, opts) {

  let limit = (opts && opts.limitValue) || 100;

  return function aParallel(context, callback) {
    let newContext = context;
    let newCallback = callback;

    // Remap the steps array to pass the context into each step.
    let newSteps = L.map(steps, function(step) {
      return function(callback){
        step(newContext, callback);
      };
    });

    // Run each of the steps in parallel.
    A.parallelLimit(
      newSteps,
      limit,
      function(err, finalContext) {
        // We don't need to do anything with the array of contexts returned from each step at the moment.
        return newCallback(err, newContext);
      });
  };
}

function isProbableEnough(obj) {
  if (typeof obj.probability === 'undefined') {
    return true;
  }

  let probability = Number(obj.probability) || 0;
  if (probability > 100) {
    probability = 100;
  }

  let r = L.random(100);
  return r < probability;
}

function template(o, context) {
  let result;

  if (typeof o === 'undefined') {
    return undefined;
  }

  if (o.constructor === Object) {
    result = traverse(o).map(function(x) {

      if (typeof x === 'string') {
        this.update(template(x, context));
      } else {
        return x;
      }
    });
  } else if (typeof o === 'string') {
    if (!/{{/.test(o)) {
      return o;
    }
    const funcCallRegex = /{{\s*(\$[A-Za-z0-9_]+\s*\(\s*.*\s*\))\s*}}/;
    let match = o.match(funcCallRegex);
    if (match) {
      // This looks like it could be a function call:
      const syntax = esprima.parse(match[1]);
      // TODO: Use a proper schema for what we expect here
      if (syntax.body && syntax.body.length === 1 &&
          syntax.body[0].type === 'ExpressionStatement') {
        let funcName = syntax.body[0].expression.callee.name;
        let args = L.map(syntax.body[0].expression.arguments, function(arg) {
          return arg.value;
        });
        if (funcName in context.funcs) {
          return template(o.replace(funcCallRegex, context.funcs[funcName].apply(null, args)), context);
        }
      }
    } else {
      if (!o.match(/{{/)) {
        return o;
      }

      result = renderVariables(o, context.vars);
    }
  } else {
    return o;
  }

  return result;
}

function renderVariables (str, vars) {
  const RX = /{{{?[\s$\w\.\[\]\'\"]+}}}?/g;
  let rxmatch;
  let result = str.substring(0, str.length);

  // Special case for handling integer/boolean/object substitution:
  //
  // Does the template string contain one variable and nothing else?
  // e.g.: "{{ myvar }" or "{{    myvar }", but NOT " {{ myvar }"
  // If so, we treat it as a special case.
  const matches = str.match(RX);
  if (matches && matches.length === 1) {
    if (matches[0] === str) {
      // there's nothing else in the template but the variable
      const varName = str.replace(/{/g, '').replace(/}/g, '').trim();
      return L.get(vars, varName) || '';
    }
  }

  while (result.search(RX) > -1) {
    let templateStr = result.match(RX)[0];
    const varName = templateStr.replace(/{/g, '').replace(/}/g, '').trim();

    let varValue = L.get(vars, varName);

    if (typeof varValue === 'object') {
      varValue = JSON.stringify(varValue);
    }
    result = result.replace(templateStr, varValue);
  }

  return result;
}

// Presume code is valid JS code (i.e. that it has been checked elsewhere)
function evil(sandbox, code) {
  let context = vm.createContext(sandbox);
  let script = new vm.Script(code);
  try {
    return script.runInContext(context);
  }
  catch(e) {
    return null;
  }
}

function parseLoopCount(countSpec) {
  let from = 0;
  let to = 0;

  if (typeof countSpec === 'number') {
    from = 0;
    to = countSpec;
  } else if (typeof countSpec === 'string') {
    if (isNaN(Number(countSpec))) {
      if (/\d\-\d/.test(countSpec)) {
        from = Number(countSpec.split('-')[0]);
        to = Number(countSpec.split('-')[1]);
      } else {
        to = 0;
      }
    } else {
      to = Number(countSpec);
    }
  } else {
    to = 0;
  }

  return { from: from, to: to };
}

//
// Calls done() with:
// {captures: { var: value }, matches: { var: {expected: '', got: ''} }}
//
function captureOrMatch(params, response, context, done) {
  let result = {
    captures: {},
    matches: {}
  };

  if (!params.capture && !params.match) {
    return done(null, result);
  }

  let specs = L.concat(
    L.get(params, 'capture', []),
    L.get(params, 'match', []));

  async.eachSeries(
    specs,
    function(spec, next) {
      let parsedSpec = parseSpec(spec, response);
      let parser = parsedSpec.parser;
      let extractor = parsedSpec.extractor;
      let expr = parsedSpec.expr;

      // are we looking at body or headers:
      var content = response.body;
      if (spec.header) {
        content = response.headers;
      }

      parser(content, function(err, doc) {
        if (err) {
          // TODO: The message could be improved still
          if (spec.strict) {
            return next(new Error(`Couldn't parse response for '${expr}'`), null);
          }
        }

        let extractedValue = extractor(doc, template(expr, context), spec);

        if (spec.value) {
          // this is a match spec
          let expected = template(spec.value, context);
          debug('match: %s, expected: %s, got: %s', expr, expected, extractedValue);
          if (extractedValue !== expected) {
            result.matches[expr] = {
              success: false,
              expected: expected,
              got: extractedValue,
              expression: expr,
              strict: spec.strict
            };
          } else {
            result.matches.expr = {
              success: true,
              expected: expected,
              expression: expr
            };
          }
          return next(null);
        }

        if (spec.as) {
          // this is a capture
          debug('capture: %s = %s', spec.as, extractedValue);
          result.captures[spec.as] = extractedValue;
          if (spec.transform) {
            let transformedValue = evil(
              result.captures,
              spec.transform);

            debug('transform: %s = %s', spec.as, result.captures[spec.as]);
            result.captures[spec.as] = transformedValue !== null ? transformedValue : extractedValue;
          }
        }

        return next(null);
      });
    },
    function(err) {
      if (err) {
        return done(err, null);
      } else {
        return done(null, result);
      }
    });
}

function parseSpec(spec, response) {
  let parser;
  let extractor;
  let expr;

  if (spec.json) {
    parser = parseJSON;
    extractor = extractJSONPath;
    expr = spec.json;
  } else if (xmlCapture && spec.xpath) {
    parser = xmlCapture.parseXML;
    extractor = xmlCapture.extractXPath;
    expr = spec.xpath;
  } else if (spec.regexp) {
    parser = dummyParser;
    extractor = extractRegExp;
    expr = spec.regexp;
  } else if (spec.header) {
    parser = dummyParser;
    extractor = extractHeader;
    expr = spec.header;
  } else if (spec.selector) {
    parser = dummyParser;
    extractor = extractCheerio;
    expr = spec.selector;
  } else {
    if (isJSON(response)) {
      parser = parseJSON;
      extractor = extractJSONPath;
      expr = spec.json;
    } else if (xmlCapture && isXML(response)) {
      parser = xmlCapture.parseXML;
      extractor = xmlCapture.extractXPath;
      expr = spec.xpath;
    } else {
      // We really don't know what to do here.
      parser = dummyParser;
      extractor = dummyExtractor;
      expr = '';
    }
  }

  return { parser: parser, extractor: extractor, expr: expr };
}

/*
 * Wrap JSON.parse in a callback
 */
function parseJSON(body, callback) {
  let r = null;
  let err = null;

  try {
    if (typeof body === 'string') {
      r = JSON.parse(body);
    } else {
      r = body;
    }
  } catch(e) {
    err = e;
  }

  return callback(err, r);
}

function dummyParser(body, callback) {
  return callback(null, body);
}

// doc is a JSON object
function extractJSONPath(doc, expr) {
  // typeof null is 'object' hence the explicit check here
  if (typeof doc !== 'object' || doc === null) {
    return '';
  }

  let results;

  try {
    results = jsonpath.query(doc, expr);
  } catch (queryErr) {
    debug(queryErr);
  }

  if (!results) {
    return '';
  }

  if (results.length > 1) {
    return results[randomInt(0, results.length - 1)];
  } else {
    return results[0];
  }
}

// doc is a string or an object (body parsed by Request when headers indicate JSON)
function extractRegExp(doc, expr, opts) {
  let group = opts.group;
  let flags = opts.flags;
  let str;
  if (typeof doc === 'string') {
    str = doc;
  } else {
    str = JSON.stringify(doc); // FIXME: not the same string as the one we got from the server
  }
  let rx;
  if (flags) {
      rx = new RegExp(expr, flags);
  } else {
      rx = new RegExp(expr);
  }
  let match = rx.exec(str);
  if (!match) {
    return '';
  }
  if(group && match[group]) {
    return match[group];
  } else if (match[0]) {
    return match[0];
  } else {
    return '';
  }
}

function extractHeader(headers, headerName) {
  return headers[headerName] || '';
}

function extractCheerio(doc, expr, opts) {
  let $ = cheerio.load(doc);
  let els = $(expr);
  let i = 0;
  if (typeof opts.index !== 'undefined') {
    if (opts.index === 'random') {
      i = Math.ceil(Math.random() * els.get().length - 1);
    } else if (opts.index === 'last') {
      i = els.get().length() - 1;
    } else if (typeof Number(opts.index) === 'number') {
      i = Number(opts.index);
    }
  }
  return els.slice(i, i + 1).attr(opts.attr);
}

function dummyExtractor() {
  return '';
}

/*
 * Given a response object determine if it's JSON
 */
function isJSON(res) {
  debug('isJSON: content-type = %s', res.headers['content-type']);
  return (res.headers['content-type'] &&
          /^application\/json/.test(res.headers['content-type']));
}

/*
 * Given a response object determine if it's some kind of XML
 */
function isXML(res) {
  return (res.headers['content-type'] &&
          (/^[a-zA-Z]+\/xml/.test(res.headers['content-type']) ||
           /^[a-zA-Z]+\/[a-zA-Z]+\+xml/.test(res.headers['content-type'])));

}

function randomInt (low, high) {
  return Math.floor(Math.random() * (high - low + 1) + low);
}
<|endoftext|>


<commit_before>/**
 * This file is part of the NovaeCalc project.
 *
 * It is permitted to use, redistribute and/or modify this software
 * under the terms of the MIT License
 *
 * @author Felix Maier <maier.felix96@gmail.com>
 * @copyright (c) 2015 Felix Maier, @felixmaier
 *
 * You may not change or remove these lines
 *
 */

"use strict";

  /**
   * Visualize the selection
   *
   * @method getSelection
   * @static
   */
  CORE.Selector.prototype.getSelection = function() {

    /** Delete selection effect of all last selected cells */
    this.deleteCellHoverEffect();

    var first = CORE.Cells.Selected.First,
        last = CORE.Cells.Selected.Last;

    /** No cells selected */
    if (!CORE.Cells.Selected.First || !CORE.Cells.Selected.Last) {
      if (this.SelectedCells.length) {
        first = this.SelectedCells[0];
        last = this.SelectedCells.length > 1 ? this.SelectedCells[this.SelectedCells.length - 1] : this.SelectedCells[0];
      }
    }

    /** Convert first selected cell into usable format */
    this.Selected.First.Letter = first.Letter;
    this.Selected.First.Number = first.Number;

    /** Convert last selected cell into usable format */
    this.Selected.Last.Letter = last.Letter;
    this.Selected.Last.Number = last.Number;

    /** Calculate space between the 2 selected cells */
    var width = CORE.$.alphaToNumber(this.Selected.Last.Letter) - CORE.$.alphaToNumber(this.Selected.First.Letter);

    /** Calculate the height between the 2 selected cells */
    var height = ( this.Selected.Last.Number - this.Selected.First.Number ) + this.Selected.First.Number;

    /** Backup variable to do object inversion */
    var backup = null;

    /** Shorter object access */
    var firstCell = this.Selected.First,
        lastCell = this.Selected.Last;

    /** If width is negative, convert into positive */
    if (width < 0) width = ( ~ width + 1 );

    /** If height is negative, convert into positive */
    if (height < 0) height = ( ~ height + 1 );

    /** Clean last selected cells */
    this.SelectedCells = [];

    /** Calculate width and height */
    width = lastCell.Letter - firstCell.Letter;

    if (lastCell.Number > firstCell.Number) {
      height = ( lastCell.Number - firstCell.Number ) + firstCell.Number;
    } else {
      height = firstCell.Number;
    }

    /** Vertical selection */
    if (width === 0 && height >= 0) {
      for (var yy = 0; yy < height; ++yy) {
        /** Positive vertical selection */
        if ( ( yy + 1 ) >= firstCell.Number) {
          this.SelectedCells.push({ letter: firstCell.Letter, number: (yy + 1) });
        }
        /** Negative vertical selection */
        else if ( ( yy + 1 ) >= lastCell.Number) {
          this.SelectedCells.push({ letter: firstCell.Letter, number: (yy + 1) });
        }
      }
    /** Horizontal selection */
    } else if (width > 0) {
      /** Positive Diagonal horizontal and vertical selection */
      if (firstCell.Number <= lastCell.Number) {
        for (var xx = 0; xx <= width; ++xx) {
          for (var yy = 0; yy < height; ++yy) {
            if ((yy + 1) >= firstCell.Number) {
              this.SelectedCells.push({ letter: (firstCell.Letter + xx), number: (yy + 1) });
            }
          }
        }
      /** Negative Diagonal horizontal and vertical selection */
      } else {

        /** Inversion START */
        backup = firstCell.Number;
        firstCell.Number = lastCell.Number;
        lastCell.Number = backup;
        /** Inversion END */

        for (var xx = 0; xx <= width; ++xx) {
          for (var yy = 0; yy < height; ++yy) {
            if ((yy + 1) >= firstCell.Number) {
              this.SelectedCells.push({ letter: (firstCell.Letter + xx), number: (yy + 1) });
            }
          }
        }
      }
    /** Horizontal negative selection */
    } else if (width < 0) {
      /** Fix negative y-axis selection */
      if (firstCell.Number > lastCell.Number) {
        firstCell.Number = (lastCell.Number - firstCell.Number) + firstCell.Number;
      }

      /** Convert negative width into positive */
      width = ( ~ width + 1 );

      /** Inversion START */
      backup = firstCell.Letter;

      firstCell.Letter = lastCell.Letter;
      lastCell.Letter = backup;

      backup = firstCell.Number;

      firstCell.Number = lastCell.Number;
      lastCell.Number = backup;
      /** Inversion END */

      for (var xx = 0; xx <= width; ++xx) {
        for (var yy = 0; yy < height; ++yy) {
          if ((yy + 1) >= lastCell.Number) {
            this.SelectedCells.push({ letter: (firstCell.Letter + xx), number: (yy + 1) });
          }
        }
      }
    }

    /** Add selection effect to all selected cells */
    this.addCellHoverEffect();

    /** Update menu items selection */
    this.menuSelection( (lastCell.Letter - 1), (lastCell.Number - 1));

    /** Clean edited cells */
    if (!CORE.Input.Mouse.Edit) CORE.Grid.cleanEditSelection();

  };

  /**
   * Select a single specific cell
   *
   * @method selectCell
   * @static
   */
  CORE.Selector.prototype.selectCell = function(letter, number) {

    /** Delete hover effect of previous cell */
    this.deleteCellHoverEffect();

    CORE.Cells.Select = {
      Letter: letter,
      Number: number
    };

    CORE.Cells.Selected.First.Letter = letter;
    CORE.Cells.Selected.First.Number = number;

    CORE.Cells.Selected.Last.Letter = letter;
    CORE.Cells.Selected.Last.Number = number;

    this.Selected.First.Letter = letter;
    this.Selected.First.Number = number;

    this.Selected.Last.Letter = letter;
    this.Selected.Last.Number = number;

    this.SelectedCells = [{
      letter: letter,
      number: number
    }];

    this.getSelection();

  };

  /**
   * Move the current selection a specific amount down
   *
   * @method moveSelection
   * @static
   */
  CORE.Selector.prototype.moveSelection = function(dir, amount) {

    var letter = this.Selected.First.Letter,
        number = this.Selected.First.Number;

    var letterResult = letter;
    var numberResult = number;

    switch (dir) {
      case "right":
        letterResult = (letter + amount);
        break;
      case "left":
        letterResult = (letter - amount);
        break;
      case "up":
        numberResult = (number - amount);
        break;
      case "down":
        numberResult = (number + amount);
        break;
    };

    /** Dont overscroll left */
    if (letterResult <= 0) {
      letterResult = 1;
      this.parentSelectedCell.Letter = letterResult;
    }

    /** Dont overscroll top */
    if (numberResult <= 0) {
      numberResult = 1;
      this.parentSelectedCell.Number = numberResult;
    }

    this.Selected.First = this.parentSelectedCell;
    this.Selected.Last = this.parentSelectedCell;

    /** Reset all key scroll axis amount */
    CORE.Grid.Settings.keyScrolledX = CORE.Grid.Settings.keyScrolledY = 0;

    /** Scroll one down, user edited cell at final bottom */
    if (dir === "down" && (CORE.Grid.Settings.keyScrolledY + number ) >= (CORE.Grid.Settings.y + CORE.Grid.Settings.scrolledY)) {
      CORE.Grid.Settings.scrolledY += 1;
      CORE.Grid.Settings.keyScrolledY -= 1;
    }

    /** Scroll one down, user edited cell at final bottom */
    if (dir === "up") {
      if (CORE.Grid.Settings.keyScrolledY + number <= (CORE.Grid.Settings.scrolledY + 1)) {
        CORE.Grid.Settings.scrolledY -= 1;
      }
      CORE.Grid.Settings.keyScrolledY += 1;
    }

    /** Update grid and menu */
    CORE.Grid.updateHeight("up", 1);
    CORE.Grid.generateMenu();

    /** Select the new cell */
    this.selectCell(letterResult, (numberResult));

  };<commit_msg>Bug fix<commit_after>/**
 * This file is part of the NovaeCalc project.
 *
 * It is permitted to use, redistribute and/or modify this software
 * under the terms of the MIT License
 *
 * @author Felix Maier <maier.felix96@gmail.com>
 * @copyright (c) 2015 Felix Maier, @felixmaier
 *
 * You may not change or remove these lines
 *
 */

"use strict";

  /**
   * Visualize the selection
   *
   * @method getSelection
   * @static
   */
  CORE.Selector.prototype.getSelection = function() {

    /** Delete selection effect of all last selected cells */
    this.deleteCellHoverEffect();

    var first = CORE.Cells.Selected.First,
        last = CORE.Cells.Selected.Last;

    /** No cells selected */
    if (!CORE.Cells.Selected.First || !CORE.Cells.Selected.Last) {
      if (this.SelectedCells.length) {
        first = this.SelectedCells[0];
        last = this.SelectedCells.length > 1 ? this.SelectedCells[this.SelectedCells.length - 1] : this.SelectedCells[0];
      }
    }

    /** Convert first selected cell into usable format */
    this.Selected.First.Letter = first.Letter;
    this.Selected.First.Number = first.Number;

    /** Convert last selected cell into usable format */
    this.Selected.Last.Letter = last.Letter;
    this.Selected.Last.Number = last.Number;

    /** Calculate space between the 2 selected cells */
    var width = CORE.$.alphaToNumber(this.Selected.Last.Letter) - CORE.$.alphaToNumber(this.Selected.First.Letter);

    /** Calculate the height between the 2 selected cells */
    var height = ( this.Selected.Last.Number - this.Selected.First.Number ) + this.Selected.First.Number;

    /** Backup variable to do object inversion */
    var backup = null;

    /** Shorter object access */
    var firstCell = this.Selected.First,
        lastCell = this.Selected.Last;

    /** If width is negative, convert into positive */
    if (width < 0) width = ( ~ width + 1 );

    /** If height is negative, convert into positive */
    if (height < 0) height = ( ~ height + 1 );

    /** Clean last selected cells */
    this.SelectedCells = [];

    /** Calculate width and height */
    width = lastCell.Letter - firstCell.Letter;

    if (lastCell.Number > firstCell.Number) {
      height = ( lastCell.Number - firstCell.Number ) + firstCell.Number;
    } else {
      height = firstCell.Number;
    }

    /** Vertical selection */
    if (width === 0 && height >= 0) {
      for (var yy = 0; yy < height; ++yy) {
        /** Positive vertical selection */
        if ( ( yy + 1 ) >= firstCell.Number) {
          this.SelectedCells.push({ letter: firstCell.Letter, number: (yy + 1) });
        }
        /** Negative vertical selection */
        else if ( ( yy + 1 ) >= lastCell.Number) {
          this.SelectedCells.push({ letter: firstCell.Letter, number: (yy + 1) });
        }
      }
    /** Horizontal selection */
    } else if (width > 0) {
      /** Positive Diagonal horizontal and vertical selection */
      if (firstCell.Number <= lastCell.Number) {
        for (var xx = 0; xx <= width; ++xx) {
          for (var yy = 0; yy < height; ++yy) {
            if ((yy + 1) >= firstCell.Number) {
              this.SelectedCells.push({ letter: (firstCell.Letter + xx), number: (yy + 1) });
            }
          }
        }
      /** Negative Diagonal horizontal and vertical selection */
      } else {

        /** Inversion START */
        backup = firstCell.Number;
        firstCell.Number = lastCell.Number;
        lastCell.Number = backup;
        /** Inversion END */

        for (var xx = 0; xx <= width; ++xx) {
          for (var yy = 0; yy < height; ++yy) {
            if ((yy + 1) >= firstCell.Number) {
              this.SelectedCells.push({ letter: (firstCell.Letter + xx), number: (yy + 1) });
            }
          }
        }
      }
    /** Horizontal negative selection */
    } else if (width < 0) {
      /** Fix negative y-axis selection */
      if (firstCell.Number > lastCell.Number) {
        firstCell.Number = (lastCell.Number - firstCell.Number) + firstCell.Number;
      }

      /** Convert negative width into positive */
      width = ( ~ width + 1 );

      /** Inversion START */
      backup = firstCell.Letter;

      firstCell.Letter = lastCell.Letter;
      lastCell.Letter = backup;

      backup = firstCell.Number;

      firstCell.Number = lastCell.Number;
      lastCell.Number = backup;
      /** Inversion END */

      for (var xx = 0; xx <= width; ++xx) {
        for (var yy = 0; yy < height; ++yy) {
          if ((yy + 1) >= lastCell.Number) {
            this.SelectedCells.push({ letter: (firstCell.Letter + xx), number: (yy + 1) });
          }
        }
      }
    }

    /** Add selection effect to all selected cells */
    this.addCellHoverEffect();

    /** Update menu items selection */
    this.menuSelection( (lastCell.Letter - 1), (lastCell.Number - 1));

    /** Clean edited cells */
    if (!CORE.Input.Mouse.Edit) CORE.Grid.cleanEditSelection();

  };

  /**
   * Select a single specific cell
   *
   * @method selectCell
   * @static
   */
  CORE.Selector.prototype.selectCell = function(letter, number) {

    /** Delete hover effect of previous cell */
    this.deleteCellHoverEffect();

    CORE.Cells.Select = {
      Letter: letter,
      Number: number
    };

    CORE.Cells.Selected.First.Letter = letter;
    CORE.Cells.Selected.First.Number = number;

    CORE.Cells.Selected.Last.Letter = letter;
    CORE.Cells.Selected.Last.Number = number;

    this.Selected.First.Letter = letter;
    this.Selected.First.Number = number;

    this.Selected.Last.Letter = letter;
    this.Selected.Last.Number = number;

    this.SelectedCells = [{
      letter: letter,
      number: number
    }];

    this.getSelection();

  };

  /**
   * Move the current selection a specific amount down
   *
   * @method moveSelection
   * @static
   */
  CORE.Selector.prototype.moveSelection = function(dir, amount) {

    var letter = this.Selected.First.Letter,
        number = this.Selected.First.Number;

    var letterResult = letter;
    var numberResult = number;

    switch (dir) {
      case "right":
        letterResult = (letter + amount);
        break;
      case "left":
        letterResult = (letter - amount);
        break;
      case "up":
        numberResult = (number - amount);
        break;
      case "down":
        numberResult = (number + amount);
        break;
    };

    /** Dont overscroll left */
    if (letterResult <= 0) {
      letterResult = 1;
      this.parentSelectedCell.Letter = letterResult;
    }

    /** Dont overscroll top */
    if (numberResult <= 0) {
      numberResult = 1;
      this.parentSelectedCell.Number = numberResult;
    }

    /** Reset all key scroll axis amount */
    CORE.Grid.Settings.keyScrolledX = CORE.Grid.Settings.keyScrolledY = 0;

    /** Scroll one down, user edited cell at final bottom */
    if (dir === "down" && (CORE.Grid.Settings.keyScrolledY + number ) >= (CORE.Grid.Settings.y + CORE.Grid.Settings.scrolledY)) {
      CORE.Grid.Settings.scrolledY += 1;
      CORE.Grid.Settings.keyScrolledY -= 1;
    }

    /** Scroll one down, user edited cell at final bottom */
    if (dir === "up") {
      if (CORE.Grid.Settings.keyScrolledY + number <= (CORE.Grid.Settings.scrolledY + 1)) {
        CORE.Grid.Settings.scrolledY -= 1;
      }
      CORE.Grid.Settings.keyScrolledY += 1;
    }

    /** Update grid and menu */
    CORE.Grid.updateHeight("up", 1);
    CORE.Grid.generateMenu();

    /** Select the new cell */
    this.selectCell(letterResult, (numberResult));

  };<|endoftext|>


<commit_before>define([
	'underscore'
	, 'app'
	, 'collections/bingo'
	// , 'text!../../../../../../../test/data/timesheet/noDaysLater.json'
	, 'text!../../../../../../../data/klarosk.json'
], function (_, App, BingoCardCollection, Klarosk) {
	return describe('Play.', function() {
		describe('Bingo Card Generator', function() {
			// FIXME how many duplicateds? See requirements and implementation again!
			it('Shouldn\'t have duplicate speechs (less than XXX% can be duplicated!)!', function() {
				var i,
					j,
					db = JSON.parse(Klarosk),
					wordCount = [],
					deckSize = 10,
					bingoCardGenerated,
					bingoCards = []
				;

				for (i = 0; i < db.length; i++)
					wordCount[db[i].speech] = 0;

				for (i = 0; i < deckSize; i++) {
					bingoCardGenerated = new BingoCardCollection();
					bingoCardGenerated.url = '../../../../../../../app/data/klarosk.json';
					bingoCardGenerated.fetch({async: false});

					bingoCards[i] = bingoCardGenerated.toJSON();
				}
// console.log(bingoCards)

				for (i = 0; i < deckSize; i++) {
					for (j = 0; j < bingoCards[i].length; j++)
						wordCount[bingoCards[i][j].speech] += 1;
				}
console.log(wordCount);

				return true;
			});

			it('Shouldn\'t have duplicate speech in *SAME* bingo card.', function() {
				return true;
			});
		});
	});
});
<commit_msg>Tests<commit_after>define([
	'underscore'
	, 'app'
	, 'collections/bingo'
	// , 'text!../../../../../../../test/data/timesheet/noDaysLater.json'
	, 'text!../../../../../../../data/klarosk.json'
], function (_, App, BingoCardCollection, Klarosk) {
	function printDeck() {
		var i,
			j,
			db = JSON.parse(Klarosk),
			wordCount = [],
			deckSize = 10,
			bingoCardGenerated,
			bingoCards = []
		;

		for (i = 0; i < db.length; i++)
			wordCount[db[i].speech] = 0;

		for (i = 0; i < deckSize; i++) {
			bingoCardGenerated = new BingoCardCollection();
			bingoCardGenerated.url = '../../../../../../../app/data/klarosk.json';
			bingoCardGenerated.fetch({async: false});

			bingoCards[i] = bingoCardGenerated.toJSON();
		}

		for (i = 0; i < deckSize; i++)
			for (j = 0; j < bingoCards[i].length; j++)
				wordCount[bingoCards[i][j].speech] += 1;

		console.log(wordCount);
	}

	printDeck();

	return describe('Play.', function() {
		describe('Bingo Card Generator', function() {
			it('Shouldn\'t have duplicate speech in *SAME* bingo card.', function() {
				return true;
			});
		});
	});
});
<|endoftext|>


<commit_before>const querystring = require('querystring');
const express = require('express');
const debug = require('debug')('server:server');
const getChartImage = require('utils/getChartImage');
const router = express.Router();

router.get('/:packages*', async function(req, res, next) {
  debug('chart image route');
  const protocol = req.protocol;
  const host = req.get('host');
  const pathname = req.originalUrl;

  const packages =
    pathname
      .replace(/^\/chart-image\//, '')
      .replace(/\.png$/, '')
      .split(',') || [];
  if (!packages.length) {
    debug('path doesnt require dynamic screencap');
    // send the fallback image for invalid urls
    res.redirect('https://npmcharts.com/images/og-image-3.png');
  } else {
    try {
      const queryString = querystring.stringify({ minimal: true });
      const packagesString = packages.join(',');
      const urlToScreencap = `${protocol}://${host}/compare/${packagesString}?${queryString}`;
      debug(`attempting to get chart image for: ${urlToScreencap}`);
      const imageBuffer = await getChartImage(urlToScreencap);
      res.contentType('image/png');
      res.send(imageBuffer);
    } catch (e) {
      debug.error('error generating dynamic image', e);
      res.status(500).send({ error: e.toString() });
    }
  }
});

module.exports = router;
<commit_msg>forward urls in request to chartImage<commit_after>const qs = require('query-string');
const express = require('express');
const debug = require('debug')('server:server');
const getChartImage = require('utils/getChartImage');
const router = express.Router();

router.get('/:packages*', async function(req, res, next) {
  debug('chart image route');
  const protocol = req.protocol;
  const host = req.get('host');
  const pathname = req.originalUrl.split('?')[0];

  const packages =
    pathname
      .replace(/^\/chart-image\//, '')
      .replace(/\.png/, '')
      .split(',') || [];
  if (!packages.length) {
    debug('path doesnt require dynamic screencap');
    // send the fallback image for invalid urls
    res.redirect('https://npmcharts.com/images/og-image-3.png');
  } else {
    try {
      const queryString = qs.stringify(
        Object.assign({}, req.query, { minimal: true }),
      );
      const packagesString = packages.join(',');
      const urlToScreencap = `${protocol}://${host}/compare/${packagesString}?${queryString}`;
      debug(`attempting to get chart image for: ${urlToScreencap}`);
      const imageBuffer = await getChartImage(urlToScreencap);
      res.contentType('image/png');
      res.send(imageBuffer);
    } catch (e) {
      debug.error('error generating dynamic image', e);
      res.status(500).send({ error: e.toString() });
    }
  }
});

module.exports = router;
<|endoftext|>


<commit_before>(function () {
	const CHART_DATA = 'chart2grid-chart'

	jQuery.fn.kendoChart2Grid =  function (options, dateFormat) {

		var self = this

		if (self.data('kendoGrid') != undefined) {
			return
		}
		var decimal = ''
		if(options != undefined) {
			decimal = options;
		} else {
			decimal = 'n2';
		}

		var Template = "#= kendo.toString(kendo.parseDate(category, 'yyyy-MM-dd'), '" + dateFormat+ "') #"

		var chart = self.data('kendoChart')
		var isUsingDataSource = false
		var data = []
		var columns = [{
			field: 'category',
			template: dateFormat == undefined ? "#= category #" : Template,
			title: " "
		}]

		if (chart.options.series.length > 0) {
			if (chart.options.series[0].data[0] instanceof Object) {
				isUsingDataSource = true

			}
		}

		chart.options.categoryAxis.categories.forEach(function (d, i) {
			var o = {}
			o[columns[0].field] = d

			chart.options.series.forEach(function (e, j) {
				var columnField = 'column' + j
				o[columnField] = d[e.field]

				if (i == 0) {
					columns.push({
						field: columnField,
						title: (e.hasOwnProperty('name') ? e.name : e.field),
						attributes: { style: "text-align:center;" },
						headerAttributes: { style: "text-align:center;" },
						//template: "#: kendo.toString(kendo.parseDate(new Date('DateId')), 'dd-MM-yyyy')#"
					})
				}
			})

			data.push(o)
		})

		chart.options.series.forEach(function (d, i) {
			var columnField = 'column' + i

			d.data.forEach(function (e, j) {
				let value = e

				if (isUsingDataSource) {
					value = e[d.field]
				}
				data[j][columnField] = kendo.toString(value, decimal);
			})
		})

		self.data(CHART_DATA, self.data('kendoChart'))

		if (self.data('kendoChart') != undefined) {
			self.data('kendoChart').destroy()
		}
	    self.empty()
	    self.kendoGrid({
	    	dataSource: {
	    		data: data
	    	},
	    	columns: columns
	    })
	}
})()<commit_msg>fixing coloumn width problem<commit_after>(function () {
	const CHART_DATA = 'chart2grid-chart'

	jQuery.fn.kendoChart2Grid =  function (options, dateFormat) {

		var self = this

		if (self.data('kendoGrid') != undefined) {
			return
		}
		var decimal = ''
		if(options != undefined) {
			decimal = options;
		} else {
			decimal = 'n2';
		}

		var Template = "#= kendo.toString(kendo.parseDate(category, 'yyyy-MM-dd'), '" + dateFormat+ "') #"

		var chart = self.data('kendoChart')
		var isUsingDataSource = false
		var data = []

		var addwidth = false
		if (chart.options.categoryAxis.categories.length > 4) {
			addwidth = true
		}

		var columns = [{
			field: 'category',
			template: dateFormat == undefined ? "#= category #" : Template,
			title: " "
		}]

		if (addwidth) {
			columns[0].width = "100px"
		}

		if (chart.options.series.length > 0) {
			if (chart.options.series[0].data[0] instanceof Object) {
				isUsingDataSource = true

			}
		}

		chart.options.categoryAxis.categories.forEach(function (d, i) {
			var o = {}
			o[columns[0].field] = d

			chart.options.series.forEach(function (e, j) {
				var columnField = 'column' + j
				o[columnField] = d[e.field]

				if (i == 0) {
					var tColumn = {
						field: columnField,
						title: (e.hasOwnProperty('name') ? e.name : e.field),
						attributes: { style: "text-align:center;" },
						headerAttributes: { style: "text-align:center;" },
						//template: "#: kendo.toString(kendo.parseDate(new Date('DateId')), 'dd-MM-yyyy')#"
					}

					if (addwidth) {
						tColumn.width = "60px"
					}

					columns.push(tColumn)
				}
			})

			data.push(o)
		})

		chart.options.series.forEach(function (d, i) {
			var columnField = 'column' + i

			d.data.forEach(function (e, j) {
				let value = e

				if (isUsingDataSource) {
					value = e[d.field]
				}
				data[j][columnField] = kendo.toString(value, decimal);
			})
		})

		self.data(CHART_DATA, self.data('kendoChart'))

		if (self.data('kendoChart') != undefined) {
			self.data('kendoChart').destroy()
		}
		// console.log(columns)
	    self.empty()
	    self.kendoGrid({
	    	dataSource: {
	    		data: data
	    	},
	    	columns: columns
	    })
	}
})()<|endoftext|>


<commit_before>function formatSubtitle(book) {
  if (book.authorName && book.publicationYear) {
    return `By ${book.authorName} (${book.publicationYear})`
  }
  return book.authorName ? `By ${book.authorName}` : book.publicationYear
}

export default {
  name: 'book',
  type: 'object',
  title: 'Book',
  description: 'This is just a simple type for generating some test data',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string'
    },
    {
      name: 'translations',
      title: 'Translations',
      type: 'object',
      fields: [
        {name: 'no', type: 'string', title: 'Norwegian (Bokmål)'},
        {name: 'nn', type: 'string', title: 'Norwegian (Nynorsk)'},
        {name: 'se', type: 'string', title: 'Swedish'}
      ]
    },
    {
      name: 'author',
      title: 'Author',
      type: 'reference',
      to: {type: 'author', title: 'Author'}
    },
    {
      name: 'coverImage',
      title: 'Cover Image',
      type: 'image'
    },
    {
      name: 'publicationYear',
      title: 'Year of publication',
      type: 'number'
    },
    {
      name: 'isbn',
      title: 'ISBN number',
      description: 'ISBN-number of the book. Not shown in studio.',
      type: 'number',
      hidden: true
    }
  ],
  orderings: [
    {
      title: 'Title',
      name: 'title',
      by: [
        {field: 'title', direction: 'asc'},
        {field: 'publicationYear', direction: 'asc'}
      ]
    },
    {
      title: 'Swedish title',
      name: 'swedishTitle',
      by: [
        {field: 'translations.se', direction: 'asc'},
        {field: 'title', direction: 'asc'}
      ]
    }
  ],
  preview: {
    select: {
      title: 'title',
      translations: 'translations',
      createdAt: '_createdAt',
      authorName: 'author.name',
      publicationYear: 'publicationYear',
      imageUrl: 'coverImage.asset.url'
    },
    prepare(book, options = {}) {
      return Object.assign({}, book, {
        title: ((options.sorting || {}).name === 'swedishTitle' && (book.translations || {}).se) || book.title,
        subtitle: formatSubtitle(book)
      })
    }
  }
}
<commit_msg>[test-studio] Fix accessed view options on wrong key<commit_after>function formatSubtitle(book) {
  if (book.authorName && book.publicationYear) {
    return `By ${book.authorName} (${book.publicationYear})`
  }
  return book.authorName ? `By ${book.authorName}` : book.publicationYear
}

export default {
  name: 'book',
  type: 'object',
  title: 'Book',
  description: 'This is just a simple type for generating some test data',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string'
    },
    {
      name: 'translations',
      title: 'Translations',
      type: 'object',
      fields: [
        {name: 'no', type: 'string', title: 'Norwegian (Bokmål)'},
        {name: 'nn', type: 'string', title: 'Norwegian (Nynorsk)'},
        {name: 'se', type: 'string', title: 'Swedish'}
      ]
    },
    {
      name: 'author',
      title: 'Author',
      type: 'reference',
      to: {type: 'author', title: 'Author'}
    },
    {
      name: 'coverImage',
      title: 'Cover Image',
      type: 'image'
    },
    {
      name: 'publicationYear',
      title: 'Year of publication',
      type: 'number'
    },
    {
      name: 'isbn',
      title: 'ISBN number',
      description: 'ISBN-number of the book. Not shown in studio.',
      type: 'number',
      hidden: true
    }
  ],
  orderings: [
    {
      title: 'Title',
      name: 'title',
      by: [
        {field: 'title', direction: 'asc'},
        {field: 'publicationYear', direction: 'asc'}
      ]
    },
    {
      title: 'Swedish title',
      name: 'swedishTitle',
      by: [
        {field: 'translations.se', direction: 'asc'},
        {field: 'title', direction: 'asc'}
      ]
    }
  ],
  preview: {
    select: {
      title: 'title',
      translations: 'translations',
      createdAt: '_createdAt',
      authorName: 'author.name',
      publicationYear: 'publicationYear',
      imageUrl: 'coverImage.asset.url'
    },
    prepare(book, options = {}) {
      return Object.assign({}, book, {
        title: ((options.ordering || {}).name === 'swedishTitle' && (book.translations || {}).se) || book.title,
        subtitle: formatSubtitle(book)
      })
    }
  }
}
<|endoftext|>