<commit_before>/* This is `mask`, a tiny MIT-licensed library providing collision masks
 * and thus pixel-perfect collision detection in JavaScript. Collision
 * masks are read from [PBM][] files. Check the [github][] page for more
 * information.
 * [github]: https://github.com/startling/mask
 * [PBM]: http://netpbm.sourceforge.net/doc/pbm.html
 */

var mask = (function () {
  /* Construct a `mask`.
   *
   * Examples:
   *
   *     new mask()
   *
   * @constructor
   * @api public
   */
  function mask () {
    this.translation = {};
    this.translation.x = 0;
    this.translation.y = 0;
    this.size = {};
    this.size.x = 0;
    this.size.y = 0;
    this.data = null;
  }
  /* Make a clone of a `mask`.
   *
   * @this {mask}
   * @api public
   */
  mask.prototype.clone = function () {
    var other = new mask();
    other.translation.x = this.translation.x;
    other.translation.y = this.translation.y;
    other.size.x = this.size.x;
    other.size.y = this.size.y;
    other.data = this.data;
    return other;
  };
  /* Translate this `mask` in two dimensions.
   *
   * @this {mask}
   * @param {Number} x x-coordinate
   * @param {Number} y y-coordinate
   * @api public
   */
  mask.prototype.translate = function (x, y) {
    this.translation.x += x;
    this.translation.y += y;
  };
  /* Make a copy of this `mask` translated to the coordinates given.
   *
   * @this {mask}
   * @param {Number} x x-coordinate
   * @param {Number} y y-coordinate
   * @api public
   */
  mask.prototype.at = function (x, y) {
    var other = this.clone();
    other.translation = {x: x, y: y};
    return other;
  };
  /** Some callbacks expect a `mask`.
   * @callback maskCallback
   * @param {mask} the collision map
   */
  /* Create a `mask` object from an `ArrayBuffer` taken as a PBM image.
   *
   * @param {ArrayBuffer} array image data
   * @param {maskCallback} callback
   * @api public
   */
  mask.fromPBM = function (array, callback) {
    var bytes = new Uint8Array(array);
    if (bytes.byteLength > 2) {
      if (bytes[0] === 'P'.charCodeAt(0)) {
        if (bytes[1] === '4'.charCodeAt(0)) {
          // This is a PBM binary file...
          return mask.fromBinaryPBM(bytes, callback);
        } else if (bytes[1] == "1".charCodeAt(0)) {
          // This is an ASCII binary file...
          return mask.fromASCIIPBM(bytes, callback);
        } else {
          // Unknown signature.
          throw new Error("Unknown or unsupported PBM signature.");
        }
      } else {
        // Unknown signature.
        throw new Error("Unknown or unsupported PBM signature.");
      }
    } else {
      // File too short.
      throw new Error("Invalid PBM image.");
    }
  };
  /* Skip every byte matching a regular expression.
   *
   * @api private
   * @param {RegExp} regex regular expression
   * @param {Uint8Buffer} bytes character data
   * @param {object} state parser state
   */
  function skipRegex (regex, bytes, state) {
    for (; state.index < bytes.byteLength; state.index++) {
      var here = String.fromCharCode(bytes[state.index]);
      if (!here.match(regex)) {
        break;
      }
    }
  }
  /* Find all the leading characters matching a regular expression and
   * stick them in an array.
   *
   * @api private
   * @param {RegExp} regex regular expression
   * @param {Uint8Buffer} bytes character data
   * @param {object} state parser state
   * @return {Array}
   */
  function accumulateRegex (regex, bytes, state) {
    var accumulator = [];
    for (; state.index < bytes.byteLength; state.index++) {
      var here = String.fromCharCode(bytes[state.index]);
      if (here.match(regex)) {
        accumulator.push(here);
      } else {
        break;
      }
    }
    return accumulator;
  }
  /* Skip the parts that come after a PBM signature -- whitespace and any
   * number of comments.
   *
   * @api private
   * @param {Uint8Buffer} bytes character data
   * @param {object} state parser state
   */
  function skipPostSignature (bytes, state) {
    skipRegex(/\s/, bytes, state);
    var here = String.fromCharCode(bytes[state.index]);
    while (here === '#') {
      state.index++;
      skipRegex(/[^\n]/, bytes, state);
      skipRegex(/\s/, bytes, state);
      here = String.fromCharCode(bytes[state.index]);
    }
  }
  /* Create a mask from an Uint8Array taken semantically as an ASCII PBM image.
   *
   * @param {Uint8Buffer} bytes PBM image data.
   * @param {maskCallback} callback
   * @api public
   */
  mask.fromASCIIPBM = function (bytes, callback) {
    var bits = [];
    var state = {index: 2};
    // Skip the spaces and comments post-signature.
    skipPostSignature(bytes, state);
    var width = accumulateRegex(/\d/, bytes, state).join("");
    skipRegex(/\s/, bytes, state);
    // Read numbers and stick them into 'height' until whitespace.
    var height = accumulateRegex(/\d/, bytes, state).join("");
    skipRegex(/\s/, bytes, state);
    // Create a mask.
    var m = new mask();
    m.size.x = parseInt(width, 10);
    m.size.y = parseInt(height, 10);
    // Read 0 and 1 until the end of the file, skipping everything else.
    for (; state.index < bytes.byteLength; state.index++) {
      function add (x) {
        if (!bits.length || bits[bits.length - 1].length === m.size.x) {
          bits.push([]);
        }
        bits[bits.length - 1].push(x);
      }
      switch (bytes[state.index]) {
      case '0'.charCodeAt(0):
        add(false);
        break;
      case '1'.charCodeAt(0):
        add(true);
        break;
      default:
        break;
      }
    }
    m.data = bits;
    return callback(m);
  };
  /* Create a `mask` from an Uint8Array taken semantically as an binary
   * PBM image.
   *
   * @param {Uint8Buffer} bytes PBM image data.
   * @param {maskCallback} callback
   * @api public
   */
  mask.fromBinaryPBM = function (bytes, callback) {
     var bits = [];
    var state = {index: 2};
    // Skip the spaces and comments post-signature.
    skipPostSignature(bytes, state);
    // Read numbers and stick them into 'width' until whitespace.
    var width = accumulateRegex(/\d/, bytes, state).join("");
    skipRegex(/\s/, bytes, state);
    // Read numbers and stick them into 'height' until whitespace.
    var height = accumulateRegex(/\d/, bytes, state).join("");
    skipRegex(/\s/, bytes, state);
    // Create a mask.
    var m = new mask();
    m.size.x = parseInt(width, 10);
    m.size.y = parseInt(height, 10);
    // Read each byte, in sequence.
    for (; state.index < bytes.byteLength; state.index++) {
      if (bits.length === m.size.y - 1 &&
          bits[bits.length - 1].length === m.size.x - 1) {
        break;
      }
      else if (!bits.length || bits[bits.length - 1].length >= m.size.x) {
        bits.push([]);
      }
      for (var b = 0; b < 8; b++) {
        if (bits[bits.length - 1].length < m.size.x) {
          var bit = Boolean((bytes[state.index] >> (7 - b)) & 0x01);
          bits[bits.length - 1].push(bit);
        }
      }
    }
    m.data = bits;
    callback(m);
  };
  /* Create a `mask` from a PBM image at some URL.
   *
   * @param {String} url A URL pointing to a PBM image.
   * @param {maskCallback} callback
   * @api public
   */
  mask.fromPBMUrl = function (url, callback) {
    var req = new XMLHttpRequest();
    req.responseType = "arraybuffer";
    req.onload = function (ev) {
      if (req.response) {
        mask.fromPBM(req.response, callback);
      } else {
        // No response.
        throw new Error("No response.");
      }
    };
    req.open("GET", url, true);
    req.send();
  };
  /* Test whether two masks collide.
   *
   * @param {mask} a
   * @param {mask} b
   * @api public
   */
  mask.collision = function (a, b) {
    var diff = {x: a.translation.x - b.translation.x,
                y: a.translation.y - b.translation.y };
    for (var x = 0; x < a.size.x; x++) {
      for (var y = 0; y < a.size.y; y++) {
        if (a.data[x][y]) {
          var forB = {x: x + diff.x, y: y + diff.y};
          if (b.data[forB.x] && b.data[forB.x][forB.y]) {
            return true;
          }
        }
      }
    }
    return false;
  };
  /* Test whether a mask is completely contained within another.
   *
   * @param {mask} a The contained mask.
   * @param {mask} b The containing mask.
   * @api public
   */
  mask.within = function (a, b) {
    var diff = {x: a.translation.x - b.translation.x,
                y: a.translation.y - b.translation.y };
    for (var x = 0; x < a.size.x; x++) {
      for (var y = 0; y < a.size.y; y++) {
        if (a.data[x][y]) {
          var forB = {x: x + diff.x, y: y + diff.y};
          if (!(b.data[forB.x] && b.data[forB.x][forB.y])) {
            return false;
          }
        }
      }
    }
    return true;
  };
  if (typeof module !== "undefined") {
    module.exports = mask;
  }
  return mask;
})();
<commit_msg>new error-handling<commit_after>/* This is `mask`, a tiny MIT-licensed library providing collision masks
 * and thus pixel-perfect collision detection in JavaScript. Collision
 * masks are read from [PBM][] files. Check the [github][] page for more
 * information.
 * [github]: https://github.com/startling/mask
 * [PBM]: http://netpbm.sourceforge.net/doc/pbm.html
 */

var mask = (function () {
  /* Construct a `mask`.
   *
   * Examples:
   *
   *     new mask()
   *
   * @constructor
   * @api public
   */
  function mask () {
    this.translation = {};
    this.translation.x = 0;
    this.translation.y = 0;
    this.size = {};
    this.size.x = 0;
    this.size.y = 0;
    this.data = null;
  }
  /* Make a clone of a `mask`.
   *
   * @this {mask}
   * @api public
   */
  mask.prototype.clone = function () {
    var other = new mask();
    other.translation.x = this.translation.x;
    other.translation.y = this.translation.y;
    other.size.x = this.size.x;
    other.size.y = this.size.y;
    other.data = this.data;
    return other;
  };
  /* Translate this `mask` in two dimensions.
   *
   * @this {mask}
   * @param {Number} x x-coordinate
   * @param {Number} y y-coordinate
   * @api public
   */
  mask.prototype.translate = function (x, y) {
    this.translation.x += x;
    this.translation.y += y;
  };
  /* Make a copy of this `mask` translated to the coordinates given.
   *
   * @this {mask}
   * @param {Number} x x-coordinate
   * @param {Number} y y-coordinate
   * @api public
   */
  mask.prototype.at = function (x, y) {
    var other = this.clone();
    other.translation = {x: x, y: y};
    return other;
  };
  /** Some callbacks expect a `mask`.
   * @callback maskCallback
   * @param {mask} the collision map
   */
  /* Create a `mask` object from an `ArrayBuffer` taken as a PBM image.
   *
   * @param {ArrayBuffer} array image data
   * @param {maskCallback} callback
   * @api public
   */
  mask.fromPBM = function (array, callback) {
    var bytes = new Uint8Array(array);
    if (bytes.byteLength > 2) {
      if (bytes[0] === 'P'.charCodeAt(0)) {
        if (bytes[1] === '4'.charCodeAt(0)) {
          // This is a PBM binary file...
          return mask.fromBinaryPBM(bytes, callback);
        } else if (bytes[1] == "1".charCodeAt(0)) {
          // This is an ASCII binary file...
          return mask.fromASCIIPBM(bytes, callback);
        } else {
          // Unknown signature.
          return callback(null, new Error("Unknown PBM signature."));
        }
      } else {
        // Unknown signature.
        return callback(null, new Error("Unknown PBM signature."));
      }
    } else {
      // File too short.
      return callback(null, Error("Invalid PBM image."));
    }
  };
  /* Skip every byte matching a regular expression.
   *
   * @api private
   * @param {RegExp} regex regular expression
   * @param {Uint8Buffer} bytes character data
   * @param {object} state parser state
   */
  function skipRegex (regex, bytes, state) {
    for (; state.index < bytes.byteLength; state.index++) {
      var here = String.fromCharCode(bytes[state.index]);
      if (!here.match(regex)) {
        break;
      }
    }
  }
  /* Find all the leading characters matching a regular expression and
   * stick them in an array.
   *
   * @api private
   * @param {RegExp} regex regular expression
   * @param {Uint8Buffer} bytes character data
   * @param {object} state parser state
   * @return {Array}
   */
  function accumulateRegex (regex, bytes, state) {
    var accumulator = [];
    for (; state.index < bytes.byteLength; state.index++) {
      var here = String.fromCharCode(bytes[state.index]);
      if (here.match(regex)) {
        accumulator.push(here);
      } else {
        break;
      }
    }
    return accumulator;
  }
  /* Skip the parts that come after a PBM signature -- whitespace and any
   * number of comments.
   *
   * @api private
   * @param {Uint8Buffer} bytes character data
   * @param {object} state parser state
   */
  function skipPostSignature (bytes, state) {
    skipRegex(/\s/, bytes, state);
    var here = String.fromCharCode(bytes[state.index]);
    while (here === '#') {
      state.index++;
      skipRegex(/[^\n]/, bytes, state);
      skipRegex(/\s/, bytes, state);
      here = String.fromCharCode(bytes[state.index]);
    }
  }
  /* Create a mask from an Uint8Array taken semantically as an ASCII PBM image.
   *
   * @param {Uint8Buffer} bytes PBM image data.
   * @param {maskCallback} callback
   * @api public
   */
  mask.fromASCIIPBM = function (bytes, callback) {
    var bits = [];
    var state = {index: 2};
    // Skip the spaces and comments post-signature.
    skipPostSignature(bytes, state);
    var width = accumulateRegex(/\d/, bytes, state).join("");
    skipRegex(/\s/, bytes, state);
    // Read numbers and stick them into 'height' until whitespace.
    var height = accumulateRegex(/\d/, bytes, state).join("");
    skipRegex(/\s/, bytes, state);
    // Create a mask.
    var m = new mask();
    m.size.x = parseInt(width, 10);
    m.size.y = parseInt(height, 10);
    // Read 0 and 1 until the end of the file, skipping everything else.
    for (; state.index < bytes.byteLength; state.index++) {
      function add (x) {
        if (!bits.length || bits[bits.length - 1].length === m.size.x) {
          bits.push([]);
        }
        bits[bits.length - 1].push(x);
      }
      switch (bytes[state.index]) {
      case '0'.charCodeAt(0):
        add(false);
        break;
      case '1'.charCodeAt(0):
        add(true);
        break;
      default:
        break;
      }
    }
    m.data = bits;
    return callback(m);
  };
  /* Create a `mask` from an Uint8Array taken semantically as an binary
   * PBM image.
   *
   * @param {Uint8Buffer} bytes PBM image data.
   * @param {maskCallback} callback
   * @api public
   */
  mask.fromBinaryPBM = function (bytes, callback) {
     var bits = [];
    var state = {index: 2};
    // Skip the spaces and comments post-signature.
    skipPostSignature(bytes, state);
    // Read numbers and stick them into 'width' until whitespace.
    var width = accumulateRegex(/\d/, bytes, state).join("");
    skipRegex(/\s/, bytes, state);
    // Read numbers and stick them into 'height' until whitespace.
    var height = accumulateRegex(/\d/, bytes, state).join("");
    skipRegex(/\s/, bytes, state);
    // Create a mask.
    var m = new mask();
    m.size.x = parseInt(width, 10);
    m.size.y = parseInt(height, 10);
    // Read each byte, in sequence.
    for (; state.index < bytes.byteLength; state.index++) {
      if (bits.length === m.size.y - 1 &&
          bits[bits.length - 1].length === m.size.x - 1) {
        break;
      }
      else if (!bits.length || bits[bits.length - 1].length >= m.size.x) {
        bits.push([]);
      }
      for (var b = 0; b < 8; b++) {
        if (bits[bits.length - 1].length < m.size.x) {
          var bit = Boolean((bytes[state.index] >> (7 - b)) & 0x01);
          bits[bits.length - 1].push(bit);
        }
      }
    }
    m.data = bits;
    callback(m);
  };
  /* Create a `mask` from a PBM image at some URL.
   *
   * @param {String} url A URL pointing to a PBM image.
   * @param {maskCallback} callback
   * @api public
   */
  mask.fromPBMUrl = function (url, callback) {
    var req = new XMLHttpRequest();
    req.responseType = "arraybuffer";
    req.onload = function (ev) {
      if (req.response) {
        return mask.fromPBM(req.response, callback);
      } else {
        // No response.
        return callback(null, new Error("No response."));
      }
    };
    req.onerror = function (e) {
      return callback(null, e);
    };
    req.open("GET", url, true);
    req.send();
  };
  /* Test whether two masks collide.
   *
   * @param {mask} a
   * @param {mask} b
   * @api public
   */
  mask.collision = function (a, b) {
    var diff = {x: a.translation.x - b.translation.x,
                y: a.translation.y - b.translation.y };
    for (var x = 0; x < a.size.x; x++) {
      for (var y = 0; y < a.size.y; y++) {
        if (a.data[x][y]) {
          var forB = {x: x + diff.x, y: y + diff.y};
          if (b.data[forB.x] && b.data[forB.x][forB.y]) {
            return true;
          }
        }
      }
    }
    return false;
  };
  /* Test whether a mask is completely contained within another.
   *
   * @param {mask} a The contained mask.
   * @param {mask} b The containing mask.
   * @api public
   */
  mask.within = function (a, b) {
    var diff = {x: a.translation.x - b.translation.x,
                y: a.translation.y - b.translation.y };
    for (var x = 0; x < a.size.x; x++) {
      for (var y = 0; y < a.size.y; y++) {
        if (a.data[x][y]) {
          var forB = {x: x + diff.x, y: y + diff.y};
          if (!(b.data[forB.x] && b.data[forB.x][forB.y])) {
            return false;
          }
        }
      }
    }
    return true;
  };
  if (typeof module !== "undefined") {
    module.exports = mask;
  }
  return mask;
})();
<|endoftext|>


<commit_before>'use strict';
const os = require('os');
const app = require('app');
const Menu = require('menu');
const BrowserWindow = require('browser-window');
const shell = require('shell');
const appName = app.getName();

function sendAction(action) {
	BrowserWindow.getFocusedWindow().webContents.send(action);
}

const darwinTpl = [
	{
		label: appName,
		submenu: [
			{
				label: `About ${appName}`,
				role: 'about'
			},
			{
				type: 'separator'
			},
			{
				label: 'Preferences...',
				accelerator: 'Cmd+,',
				click() {
					sendAction('show-preferences');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Log Out',
				click() {
					sendAction('log-out');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Services',
				role: 'services',
				submenu: []
			},
			{
				type: 'separator'
			},
			{
				label: `Hide ${appName}`,
				accelerator: 'Cmd+H',
				role: 'hide'
			},
			{
				label: 'Hide Others',
				accelerator: 'Cmd+Shift+H',
				role: 'hideothers'
			},
			{
				label: 'Show All',
				role: 'unhide'
			},
			{
				type: 'separator'
			},
			{
				label: `Quit ${appName}`,
				accelerator: 'Cmd+Q',
				click() {
					app.quit();
				}
			}
		]
	},
	{
		label: 'File',
		submenu: [
			{
				label: 'New Conversation',
				accelerator: 'CmdOrCtrl+N',
				click() {
					sendAction('new-conversation');
				}
			}
		]
	},
	{
		label: 'Edit',
		submenu: [
			{
				label: 'Undo',
				accelerator: 'CmdOrCtrl+Z',
				role: 'undo'
			},
			{
				label: 'Redo',
				accelerator: 'Shift+CmdOrCtrl+Z',
				role: 'redo'
			},
			{
				type: 'separator'
			},
			{
				label: 'Cut',
				accelerator: 'CmdOrCtrl+X',
				role: 'cut'
			},
			{
				label: 'Copy',
				accelerator: 'CmdOrCtrl+C',
				role: 'copy'
			},
			{
				label: 'Paste',
				accelerator: 'CmdOrCtrl+V',
				role: 'paste'
			},
			{
				label: 'Select All',
				accelerator: 'CmdOrCtrl+A',
				role: 'selectall'
			}
		]
	},
	{
		label: 'Window',
		role: 'window',
		submenu: [
			{
				label: 'Minimize',
				accelerator: 'CmdOrCtrl+M',
				role: 'minimize'
			},
			{
				label: 'Close',
				accelerator: 'CmdOrCtrl+W',
				role: 'close'
			},
			{
				type: 'separator'
			},
			{
				label: 'Bring All to Front',
				role: 'front'
			},
			// temp workaround for:
			// https://github.com/sindresorhus/caprine/issues/5
			{
				label: 'Toggle Full Screen',
				accelerator: 'Ctrl+Cmd+F',
				click() {
					const win = BrowserWindow.getFocusedWindow();
					win.setFullScreen(!win.isFullScreen());
				}
			}
		]
	},
	{
		label: 'Help',
		role: 'help'
	}
];

const linuxTpl = [
	{
		label: 'File',
		submenu: [
			{
				label: 'New Conversation',
				accelerator: 'CmdOrCtrl+N',
				click() {
					sendAction('new-conversation');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Log Out',
				click() {
					sendAction('log-out');
				}
			}
		]
	},
	{
		label: 'Edit',
		submenu: [
			{
				label: 'Cut',
				accelerator: 'CmdOrCtrl+X',
				role: 'cut'
			},
			{
				label: 'Copy',
				accelerator: 'CmdOrCtrl+C',
				role: 'copy'
			},
			{
				label: 'Paste',
				accelerator: 'CmdOrCtrl+V',
				role: 'paste'
			},
			{
				type: 'separator'
			},
			{
				label: 'Preferences',
				accelerator: 'CmdOrCtrl+,',
				click() {
					sendAction('show-preferences');
				}
			}
		]
	},
	{
		label: 'Help',
		role: 'help'
	}
];

const helpSubmenu = [
	{
		label: `${appName} Website...`,
		click() {
			shell.openExternal('https://github.com/sindresorhus/caprine');
		}
	},
	{
		label: 'Report an Issue...',
		click() {
			const body = `
**Please succinctly describe your issue and steps to reproduce it.**

-

${app.getName()} ${app.getVersion()}
${process.platform} ${process.arch} ${os.release()}`;

			shell.openExternal(`https://github.com/sindresorhus/caprine/issues/new?body=${encodeURIComponent(body)}`);
		}
	}
];

let tpl;
if (process.platform === 'darwin') {
	tpl = darwinTpl;
} else if (process.platform === 'linux') {
	tpl = linuxTpl;
}

tpl[tpl.length - 1].submenu = helpSubmenu;

module.exports = Menu.buildFromTemplate(tpl);
<commit_msg>restore the window if menu actions are triggered when minimized - fixes #16<commit_after>'use strict';
const os = require('os');
const app = require('app');
const Menu = require('menu');
const BrowserWindow = require('browser-window');
const shell = require('shell');
const appName = app.getName();

function sendAction(action) {
	const win = BrowserWindow.getAllWindows()[0];
	win.restore();
	win.webContents.send(action);
}

const darwinTpl = [
	{
		label: appName,
		submenu: [
			{
				label: `About ${appName}`,
				role: 'about'
			},
			{
				type: 'separator'
			},
			{
				label: 'Preferences...',
				accelerator: 'Cmd+,',
				click() {
					sendAction('show-preferences');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Log Out',
				click() {
					sendAction('log-out');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Services',
				role: 'services',
				submenu: []
			},
			{
				type: 'separator'
			},
			{
				label: `Hide ${appName}`,
				accelerator: 'Cmd+H',
				role: 'hide'
			},
			{
				label: 'Hide Others',
				accelerator: 'Cmd+Shift+H',
				role: 'hideothers'
			},
			{
				label: 'Show All',
				role: 'unhide'
			},
			{
				type: 'separator'
			},
			{
				label: `Quit ${appName}`,
				accelerator: 'Cmd+Q',
				click() {
					app.quit();
				}
			}
		]
	},
	{
		label: 'File',
		submenu: [
			{
				label: 'New Conversation',
				accelerator: 'CmdOrCtrl+N',
				click() {
					sendAction('new-conversation');
				}
			}
		]
	},
	{
		label: 'Edit',
		submenu: [
			{
				label: 'Undo',
				accelerator: 'CmdOrCtrl+Z',
				role: 'undo'
			},
			{
				label: 'Redo',
				accelerator: 'Shift+CmdOrCtrl+Z',
				role: 'redo'
			},
			{
				type: 'separator'
			},
			{
				label: 'Cut',
				accelerator: 'CmdOrCtrl+X',
				role: 'cut'
			},
			{
				label: 'Copy',
				accelerator: 'CmdOrCtrl+C',
				role: 'copy'
			},
			{
				label: 'Paste',
				accelerator: 'CmdOrCtrl+V',
				role: 'paste'
			},
			{
				label: 'Select All',
				accelerator: 'CmdOrCtrl+A',
				role: 'selectall'
			}
		]
	},
	{
		label: 'Window',
		role: 'window',
		submenu: [
			{
				label: 'Minimize',
				accelerator: 'CmdOrCtrl+M',
				role: 'minimize'
			},
			{
				label: 'Close',
				accelerator: 'CmdOrCtrl+W',
				role: 'close'
			},
			{
				type: 'separator'
			},
			{
				label: 'Bring All to Front',
				role: 'front'
			},
			// temp workaround for:
			// https://github.com/sindresorhus/caprine/issues/5
			{
				label: 'Toggle Full Screen',
				accelerator: 'Ctrl+Cmd+F',
				click() {
					const win = BrowserWindow.getAllWindows()[0];
					win.setFullScreen(!win.isFullScreen());
				}
			}
		]
	},
	{
		label: 'Help',
		role: 'help'
	}
];

const linuxTpl = [
	{
		label: 'File',
		submenu: [
			{
				label: 'New Conversation',
				accelerator: 'CmdOrCtrl+N',
				click() {
					sendAction('new-conversation');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Log Out',
				click() {
					sendAction('log-out');
				}
			}
		]
	},
	{
		label: 'Edit',
		submenu: [
			{
				label: 'Cut',
				accelerator: 'CmdOrCtrl+X',
				role: 'cut'
			},
			{
				label: 'Copy',
				accelerator: 'CmdOrCtrl+C',
				role: 'copy'
			},
			{
				label: 'Paste',
				accelerator: 'CmdOrCtrl+V',
				role: 'paste'
			},
			{
				type: 'separator'
			},
			{
				label: 'Preferences',
				accelerator: 'CmdOrCtrl+,',
				click() {
					sendAction('show-preferences');
				}
			}
		]
	},
	{
		label: 'Help',
		role: 'help'
	}
];

const helpSubmenu = [
	{
		label: `${appName} Website...`,
		click() {
			shell.openExternal('https://github.com/sindresorhus/caprine');
		}
	},
	{
		label: 'Report an Issue...',
		click() {
			const body = `
**Please succinctly describe your issue and steps to reproduce it.**

-

${app.getName()} ${app.getVersion()}
${process.platform} ${process.arch} ${os.release()}`;

			shell.openExternal(`https://github.com/sindresorhus/caprine/issues/new?body=${encodeURIComponent(body)}`);
		}
	}
];

let tpl;
if (process.platform === 'darwin') {
	tpl = darwinTpl;
} else if (process.platform === 'linux') {
	tpl = linuxTpl;
}

tpl[tpl.length - 1].submenu = helpSubmenu;

module.exports = Menu.buildFromTemplate(tpl);
<|endoftext|>


<commit_before>import {Tooltip} from "./tooltip"
import {elt, insertCSS} from "../dom"
import {defineParamHandler} from "../edit"
import sortedInsert from "../util/sortedinsert"

export class Menu {
  constructor(pm, display) {
    this.display = display
    this.stack = []
    this.pm = pm
  }

  show(content, displayInfo) {
    this.stack.length = 0
    this.enter(content, displayInfo)
  }

  reset() {
    this.stack.length = 0
    this.display.reset()
  }

  enter(content, displayInfo) {
    let pieces = [], explore = value => {
      if (Array.isArray(value)) {
        for (let i = 0; i < value.length; i++) explore(value[i])
        pieces.push(separator)
      } else if (!value.select || value.select(this.pm)) {
        pieces.push(value)
      }
    }
    explore(content)
    // Remove superfluous separators
    for (let i = 0; i < pieces.length; i++)
      if (pieces[i] == separator && (i == 0 || i == pieces.length - 1 || pieces[i + 1] == separator))
        pieces.splice(i--, 1)

    if (!pieces.length) return this.display.clear()

    this.stack.push(pieces)
    this.draw(displayInfo)
  }

  get active() {
    return this.stack.length > 1
  }

  draw(displayInfo) {
    let cur = this.stack[this.stack.length - 1]
    let rendered = elt("div", {class: "ProseMirror-menu"}, cur.map(item => renderItem(item, this)))
    if (this.stack.length > 1)
      this.display.enter(rendered, () => this.leave(), displayInfo)
    else
      this.display.show(rendered, displayInfo)
  }

  leave() {
    this.stack.pop()
    if (this.stack.length)
      this.draw()
    else
      this.display.reset()
  }
}

export class TooltipDisplay {
  constructor(tooltip, resetFunc) {
    this.tooltip = tooltip
    this.resetFunc = resetFunc
  }

  clear() {
    this.tooltip.close()
  }

  reset() {
    if (this.resetFunc) this.resetFunc()
    else this.clear()
  }

  show(dom, info) {
    this.tooltip.open(dom, info)
  }

  enter(dom, back, info) {
    let button = elt("div", {class: "ProseMirror-tooltip-back", title: "Back"})
    button.addEventListener("mousedown", e => {
      e.preventDefault(); e.stopPropagation()
      back()
    })
    this.show(elt("div", {class: "ProseMirror-tooltip-back-wrapper"}, dom, button), info)
  }
}

function title(pm, command) {
  let key = pm.keyForCommand(command.name)
  return key ? command.label + " (" + key + ")" : command.label
}

function renderIcon(command, menu) {
  let iconClass = "ProseMirror-menuicon"
  if (command.active(menu.pm)) iconClass += " ProseMirror-menuicon-active"

  let dom = elt("div", {class: iconClass, title: title(menu.pm, command)},
                elt("span", {class: "ProseMirror-menuicon ProseMirror-icon-" + command.name}))
  dom.addEventListener("mousedown", e => {
    e.preventDefault(); e.stopPropagation()
    if (!command.params.length) {
      command.exec(menu.pm)
      menu.reset()
    } else if (command.params.length == 1 && command.params[0].type == "select") {
      showSelectMenu(menu.pm, command, dom)
    } else {
      menu.enter(readParams(command))
    }
  })
  return dom
}

function renderSelect(item, menu) {
  let param = item.params[0]
  let value = !param.default ? null : param.default.call ? param.default(menu.pm) : param.default

  let dom = elt("div", {class: "ProseMirror-select ProseMirror-select-command-" + item.name, title: item.label},
                !value ? (param.defaultLabel || "Select...") : value.display ? value.display(value) : value.label)
  dom.addEventListener("mousedown", e => {
    e.preventDefault(); e.stopPropagation()
    showSelectMenu(menu.pm, item, dom)
  })
  return dom
}

export function showSelectMenu(pm, item, dom) {
  let param = item.params[0]
  let options = param.options.call ? param.options(pm) : param.options
  let menu = elt("div", {class: "ProseMirror-select-menu"}, options.map(o => {
    let dom = elt("div", null, o.display ? o.display(o) : o.label)
    dom.addEventListener("mousedown", e => {
      e.preventDefault()
      item.exec(pm, [o.value])
      finish()
    })
    return dom
  }))
  let pos = dom.getBoundingClientRect(), box = pm.wrapper.getBoundingClientRect()
  menu.style.left = (pos.left - box.left - 2) + "px"
  menu.style.top = (pos.top - box.top - 2) + "px"

  let done = false
  function finish() {
    if (done) return
    done = true
    document.body.removeEventListener("mousedown", finish)
    document.body.removeEventListener("keydown", finish)
    pm.wrapper.removeChild(menu)
  }
  document.body.addEventListener("mousedown", finish)
  document.body.addEventListener("keydown", finish)
  pm.wrapper.appendChild(menu)
}

function renderItem(item, menu) {
  if (item.display == "icon") return renderIcon(item, menu)
  else if (item.display == "select") return renderSelect(item, menu)
  else if (!item.display) throw new Error("Command " + item.name + " can not be shown in a menu")
  else return item.display(menu)
}

function buildParamForm(pm, command) {
  let prefill = command.info.prefillParams && command.info.prefillParams(pm)
  let fields = command.params.map((param, i) => {
    let field, name = "field_" + i
    let val = prefill ? prefill[i] : param.default || ""
    if (param.type == "text")
      field = elt("input", {name, type: "text",
                            placeholder: param.name,
                            value: val,
                            autocomplete: "off"})
    else if (param.type == "select")
      field = elt("select", {name}, (param.options.call ? param.options(pm) : param.options)
                  .map(o => elt("option", {value: o.value, selected: o == val}, o.label)))
    else // FIXME more types
      throw new Error("Unsupported parameter type: " + param.type)
    return elt("div", null, field)
  })
  return elt("form", null, fields)
}

function gatherParams(pm, command, form) {
  let bad = false
  let params = command.params.map((param, i) => {
    let val = form.elements["field_" + i].value
    if (val) return val
    if (param.default == null) bad = true
    else return param.default.call ? param.default(pm) : param.default
  })
  return bad ? null : params
}

function paramForm(pm, command, callback) {
  let form = buildParamForm(pm, command), done = false

  let finish = result => {
    if (!done) {
      done = true
      callback(result)
    }
  }

  let submit = () => {
    // FIXME error messages
    finish(gatherParams(pm, command, form, pm))
  }
  form.addEventListener("submit", e => {
    e.preventDefault()
    submit()
  })
  form.addEventListener("keydown", e => {
    if (e.keyCode == 27) {
      finish(null)
    } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) {
      e.preventDefault()
      submit()
    }
  })
  // FIXME too hacky?
  setTimeout(() => {
    let input = form.querySelector("input, textarea")
    if (input) input.focus()
  }, 20)

  return form
}

export function readParams(command) {
  return {display(menu) {
    return paramForm(menu.pm, command, params => {
      menu.pm.focus()
      if (params) {
        command.exec(menu.pm, params)
        menu.reset()
      } else {
        menu.leave()
      }
    })
  }}
}

const separator = {
  display() { return elt("div", {class: "ProseMirror-menuseparator"}) }
}

export function commandGroups(pm, ...names) {
  return names.map(group => {
    let found = []
    for (let name in pm.commands) {
      let cmd = pm.commands[name]
      if (cmd.info.menuGroup && cmd.info.menuGroup == group)
        sortedInsert(found, cmd, (a, b) => (a.info.menuRank || 50) - (b.info.menuRank || 50))
    }
    return found
  })
}

// Awkward hack to force Chrome to initialize the font and not return
// incorrect size information the first time it is used.

let forced = false
export function forceFontLoad(pm) {
  if (forced) return
  forced = true

  let node = pm.wrapper.appendChild(elt("div", {class: "ProseMirror-menuicon ProseMirror-icon-strong",
                                                style: "visibility: hidden; position: absolute"}))
  window.setTimeout(() => pm.wrapper.removeChild(node), 20)
}

function tooltipParamHandler(pm, command, callback) {
  let tooltip = new Tooltip(pm, "center")
  tooltip.open(paramForm(pm, command, params => {
    pm.focus()
    tooltip.close()
    callback(params)
  }))
}

defineParamHandler("default", tooltipParamHandler)
defineParamHandler("tooltip", tooltipParamHandler)

// FIXME check for obsolete styles
insertCSS(`

.ProseMirror-menu {
  margin: 0 -4px;
  line-height: 1;
  white-space: pre;
}
.ProseMirror-tooltip .ProseMirror-menu {
  width: -webkit-fit-content;
  width: fit-content;
}

.ProseMirror-tooltip-back-wrapper {
  padding-left: 12px;
}
.ProseMirror-tooltip-back {
  position: absolute;
  top: 5px; left: 5px;
  cursor: pointer;
}
.ProseMirror-tooltip-back:after {
  content: "«";
}

.ProseMirror-menuicon {
  display: inline-block;
  padding: 1px 4px;
  margin: 0 2px;
  cursor: pointer;
  text-rendering: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  vertical-align: middle;
}

.ProseMirror-menuicon-active {
  background: #666;
  border-radius: 4px;
}

.ProseMirror-menuseparator {
  display: inline-block;
}
.ProseMirror-menuseparator:after {
  content: "︙";
  opacity: 0.5;
  padding: 0 4px;
  vertical-align: middle;
}

.ProseMirror-select, .ProseMirror-select-menu {
  border: 1px solid #777;
  border-radius: 3px;
  font-size: 90%;
}

.ProseMirror-select {
  padding: 1px 12px 1px 4px;
  display: inline-block;
  vertical-align: middle;
  position: relative;
  cursor: pointer;
  margin: 0 4px;
}

.ProseMirror-select-command-textblockType {
  min-width: 3.2em;
}

.ProseMirror-select:after {
  content: "▿";
  color: #777;
  position: absolute;
  right: 4px;
}

.ProseMirror-select-menu {
  position: absolute;
  background: #444;
  color: white;
  padding: 2px 2px;
  z-index: 15;
}
.ProseMirror-select-menu div {
  cursor: pointer;
  padding: 0 1em 0 2px;
}
.ProseMirror-select-menu div:hover {
  background: #777;
}

`)
<commit_msg>Update menu.js<commit_after>import {Tooltip} from "./tooltip"
import {elt, insertCSS} from "../dom"
import {defineParamHandler} from "../edit"
import sortedInsert from "../util/sortedinsert"

export class Menu {
  constructor(pm, display) {
    this.display = display
    this.stack = []
    this.pm = pm
  }

  show(content, displayInfo) {
    this.stack.length = 0
    this.enter(content, displayInfo)
  }

  reset() {
    this.stack.length = 0
    this.display.reset()
  }

  enter(content, displayInfo) {
    let pieces = [], explore = value => {
      if (Array.isArray(value)) {
        for (let i = 0; i < value.length; i++) explore(value[i])
        pieces.push(separator)
      } else if (!value.select || value.select(this.pm)) {
        pieces.push(value)
      }
    }
    explore(content)
    // Remove superfluous separators
    for (let i = 0; i < pieces.length; i++)
      if (pieces[i] == separator && (i == 0 || i == pieces.length - 1 || pieces[i + 1] == separator))
        pieces.splice(i--, 1)

    if (!pieces.length) return this.display.clear()

    this.stack.push(pieces)
    this.draw(displayInfo)
  }

  get active() {
    return this.stack.length > 1
  }

  draw(displayInfo) {
    let cur = this.stack[this.stack.length - 1]
    let rendered = elt("div", {class: "ProseMirror-menu"}, cur.map(item => renderItem(item, this)))
    if (this.stack.length > 1)
      this.display.enter(rendered, () => this.leave(), displayInfo)
    else
      this.display.show(rendered, displayInfo)
  }

  leave() {
    this.stack.pop()
    if (this.stack.length)
      this.draw()
    else
      this.display.reset()
  }
}

export class TooltipDisplay {
  constructor(tooltip, resetFunc) {
    this.tooltip = tooltip
    this.resetFunc = resetFunc
  }

  clear() {
    this.tooltip.close()
  }

  reset() {
    if (this.resetFunc) this.resetFunc()
    else this.clear()
  }

  show(dom, info) {
    this.tooltip.open(dom, info)
  }

  enter(dom, back, info) {
    let button = elt("div", {class: "ProseMirror-tooltip-back", title: "Back"})
    button.addEventListener("mousedown", e => {
      e.preventDefault(); e.stopPropagation()
      back()
    })
    this.show(elt("div", {class: "ProseMirror-tooltip-back-wrapper"}, dom, button), info)
  }
}

function title(pm, command) {
  let key = pm.keyForCommand(command.name)
  return key ? command.label + " (" + key + ")" : command.label
}

function renderIcon(command, menu) {
  let iconClass = "ProseMirror-menuicon"
  if (command.active(menu.pm)) iconClass += " ProseMirror-menuicon-active"

  let dom = elt("div", {class: iconClass, title: title(menu.pm, command)},
                elt("span", {class: "ProseMirror-menuicon ProseMirror-icon-" + command.name}))
  dom.addEventListener("mousedown", e => {
    e.preventDefault(); e.stopPropagation()
    if (!command.params.length) {
      command.exec(menu.pm)
      menu.reset()
    } else if (command.params.length == 1 && command.params[0].type == "select") {
      showSelectMenu(menu.pm, command, dom)
    } else {
      menu.enter(readParams(command))
    }
  })
  return dom
}

function renderSelect(item, menu) {
  let param = item.params[0]
  let value = !param.default ? null : param.default.call ? param.default(menu.pm) : param.default

  let dom = elt("div", {class: "ProseMirror-select ProseMirror-select-command-" + item.name, title: item.label},
                !value ? (param.defaultLabel || "Select...") : value.display ? value.display(value) : value.label)
  dom.addEventListener("mousedown", e => {
    e.preventDefault(); e.stopPropagation()
    showSelectMenu(menu.pm, item, dom)
  })
  return dom
}

export function showSelectMenu(pm, item, dom) {
  let param = item.params[0]
  let options = param.options.call ? param.options(pm) : param.options
  let menu = elt("div", {class: "ProseMirror-select-menu"}, options.map(o => {
    let dom = elt("div", null, o.display ? o.display(o) : o.label)
    dom.addEventListener("mousedown", e => {
      e.preventDefault()
      item.exec(pm, [o.value])
      finish()
    })
    return dom
  }))
  let pos = dom.getBoundingClientRect(), box = pm.wrapper.getBoundingClientRect()
  menu.style.left = (pos.left - box.left - 2) + "px"
  menu.style.top = (pos.top - box.top - 2) + "px"

  let done = false
  function finish() {
    if (done) return
    done = true
    document.body.removeEventListener("mousedown", finish)
    document.body.removeEventListener("keydown", finish)
    pm.wrapper.removeChild(menu)
  }
  document.body.addEventListener("mousedown", finish)
  document.body.addEventListener("keydown", finish)
  pm.wrapper.appendChild(menu)
}

function renderItem(item, menu) {
  if (item.display == "icon") return renderIcon(item, menu)
  else if (item.display == "select") return renderSelect(item, menu)
  else if (!item.display) throw new Error("Command " + item.name + " can not be shown in a menu")
  else return item.display(menu)
}

function buildParamForm(pm, command) {
  let prefill = command.info.prefillParams && command.info.prefillParams(pm)
  let fields = command.params.map((param, i) => {
    let field, name = "field_" + i
    let val = prefill ? prefill[i] : param.default || ""
    if (param.type == "text")
      field = elt("input", {name, type: "text",
                            placeholder: param.name,
                            value: val,
                            autocomplete: "off"})
    else if (param.type == "select")
      field = elt("select", {name}, (param.options.call ? param.options(pm) : param.options)
                  .map(o => elt("option", {value: o.value, selected: o == val}, o.label)))
    else // FIXME more types
      throw new Error("Unsupported parameter type: " + param.type)
    return elt("div", null, field)
  })
  return elt("form", null, fields)
}

function gatherParams(pm, command, form) {
  let bad = false
  let params = command.params.map((param, i) => {
    let val = form.elements["field_" + i].value
    if (val) return val
    if (param.default == null) bad = true
    else return param.default.call ? param.default(pm) : param.default
  })
  return bad ? null : params
}

function paramForm(pm, command, callback) {
  let form = buildParamForm(pm, command), done = false

  let finish = result => {
    if (!done) {
      done = true
      callback(result)
    }
  }

  let submit = () => {
    // FIXME error messages
    finish(gatherParams(pm, command, form))
  }
  form.addEventListener("submit", e => {
    e.preventDefault()
    submit()
  })
  form.addEventListener("keydown", e => {
    if (e.keyCode == 27) {
      finish(null)
    } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) {
      e.preventDefault()
      submit()
    }
  })
  // FIXME too hacky?
  setTimeout(() => {
    let input = form.querySelector("input, textarea")
    if (input) input.focus()
  }, 20)

  return form
}

export function readParams(command) {
  return {display(menu) {
    return paramForm(menu.pm, command, params => {
      menu.pm.focus()
      if (params) {
        command.exec(menu.pm, params)
        menu.reset()
      } else {
        menu.leave()
      }
    })
  }}
}

const separator = {
  display() { return elt("div", {class: "ProseMirror-menuseparator"}) }
}

export function commandGroups(pm, ...names) {
  return names.map(group => {
    let found = []
    for (let name in pm.commands) {
      let cmd = pm.commands[name]
      if (cmd.info.menuGroup && cmd.info.menuGroup == group)
        sortedInsert(found, cmd, (a, b) => (a.info.menuRank || 50) - (b.info.menuRank || 50))
    }
    return found
  })
}

// Awkward hack to force Chrome to initialize the font and not return
// incorrect size information the first time it is used.

let forced = false
export function forceFontLoad(pm) {
  if (forced) return
  forced = true

  let node = pm.wrapper.appendChild(elt("div", {class: "ProseMirror-menuicon ProseMirror-icon-strong",
                                                style: "visibility: hidden; position: absolute"}))
  window.setTimeout(() => pm.wrapper.removeChild(node), 20)
}

function tooltipParamHandler(pm, command, callback) {
  let tooltip = new Tooltip(pm, "center")
  tooltip.open(paramForm(pm, command, params => {
    pm.focus()
    tooltip.close()
    callback(params)
  }))
}

defineParamHandler("default", tooltipParamHandler)
defineParamHandler("tooltip", tooltipParamHandler)

// FIXME check for obsolete styles
insertCSS(`

.ProseMirror-menu {
  margin: 0 -4px;
  line-height: 1;
  white-space: pre;
}
.ProseMirror-tooltip .ProseMirror-menu {
  width: -webkit-fit-content;
  width: fit-content;
}

.ProseMirror-tooltip-back-wrapper {
  padding-left: 12px;
}
.ProseMirror-tooltip-back {
  position: absolute;
  top: 5px; left: 5px;
  cursor: pointer;
}
.ProseMirror-tooltip-back:after {
  content: "«";
}

.ProseMirror-menuicon {
  display: inline-block;
  padding: 1px 4px;
  margin: 0 2px;
  cursor: pointer;
  text-rendering: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  vertical-align: middle;
}

.ProseMirror-menuicon-active {
  background: #666;
  border-radius: 4px;
}

.ProseMirror-menuseparator {
  display: inline-block;
}
.ProseMirror-menuseparator:after {
  content: "︙";
  opacity: 0.5;
  padding: 0 4px;
  vertical-align: middle;
}

.ProseMirror-select, .ProseMirror-select-menu {
  border: 1px solid #777;
  border-radius: 3px;
  font-size: 90%;
}

.ProseMirror-select {
  padding: 1px 12px 1px 4px;
  display: inline-block;
  vertical-align: middle;
  position: relative;
  cursor: pointer;
  margin: 0 4px;
}

.ProseMirror-select-command-textblockType {
  min-width: 3.2em;
}

.ProseMirror-select:after {
  content: "▿";
  color: #777;
  position: absolute;
  right: 4px;
}

.ProseMirror-select-menu {
  position: absolute;
  background: #444;
  color: white;
  padding: 2px 2px;
  z-index: 15;
}
.ProseMirror-select-menu div {
  cursor: pointer;
  padding: 0 1em 0 2px;
}
.ProseMirror-select-menu div:hover {
  background: #777;
}

`)
<|endoftext|>


<commit_before>'use strict';
const path = require('path');
const electron = require('electron');
const os = require('os');
const fs = require('fs-extra');

const join = path.join;
const app = electron.app;
const shell = electron.shell;
const appName = app.getName();
const BrowserWindow = electron.BrowserWindow;

let configData;
const oaJSON = '.ao.json'; // Config file name
const homeDir = os.homedir();
const homeConfig = join(homeDir, oaJSON); // Config file on home directory
const defaultConfig = join(__dirname, '.', oaJSON); // Default config file directory

const sourceURL = 'https://github.com/klauscfhq/ao';
const homepageURL = 'https://github.com/klauscfhq/ao';
const issueURL = 'https://github.com/klauscfhq/ao/issues/new';
const releaseURL = 'https://github.com/klauscfhq/ao/releases/latest';

function getConfig() {
  // Create a new default config file
  // if it does not already exist
  if (!fs.existsSync(homeConfig)) {
    try {
      fs.copySync(defaultConfig, homeConfig);
      console.log('Ao config file was created successfully');
    } catch (err) {
      console.error(err);
    }
  }
  // Parse the content of the config file
  try {
    configData = JSON.parse(fs.readFileSync(homeConfig, 'utf8'));
  } catch (err) {
    console.log('Invalid JSON object');
  }
  return configData;
}

// Get the user-defined settings
const aoConfig = getConfig();

function setAcc(custom, predifined) {
  // Return the custom or predifined shortcut keys
  if (Object.prototype.hasOwnProperty.call(aoConfig.shortcutKeys, custom)) {
    return aoConfig.shortcutKeys[custom];
  }
  return predifined;
}

function activate(custom) {
  const appWindow = BrowserWindow.getAllWindows()[0];
  // Extra measure in order to be shown
  appWindow.show();
  appWindow.webContents.send(custom);
}

const helpSubmenu = [{
  label: `Ao Homepage`,
  click() {
    shell.openExternal(homepageURL);
  }
}, {
  type: 'separator'
}, {
  label: 'Fork Source',
  click() {
    shell.openExternal(sourceURL);
  }
}, {
  label: `Report Issue`,
  click() {
    shell.openExternal(issueURL);
  }
}, {
  label: `Latest Release`,
  click() {
    shell.openExternal(releaseURL);
  }
}];

if (process.platform !== 'darwin') {
  helpSubmenu.push({
    type: 'separator'
  }, {
    role: 'about',
    click() {
      electron.dialog.showMessageBox({
        title: `About Ao`,
        message: `Ao ${app.getVersion()}`,
        detail: 'Coded with love by Klaus Sinani',
        icon: path.join(__dirname, 'static/Icon.png'),
        buttons: []
      });
    }
  });
}

const darwinTpl = [{
  label: appName,
  submenu: [{
    role: 'about'
  }, {
    type: 'separator'
  }, {
    role: 'services',
    submenu: []
  }, {
    type: 'separator'
  }, {
    role: 'hide'
  }, {
    role: 'hideothers'
  }, {
    role: 'unhide'
  }, {
    type: 'separator'
  }, {
    role: 'quit'
  }]
}, {
  label: 'File',
  submenu: [{
    label: 'Search',
    accelerator: 'CmdorCtrl+F',
    click() {
      activate('search');
    }
  }, {
    type: 'separator'
  }, {
    label: 'List',
    submenu: [{
      label: 'New List',
      accelerator: setAcc('new-list', 'CmdorCtrl+L'),
      click() {
        activate('new-list');
      }
    }, {
      label: 'Delete List',
      accelerator: setAcc('delete-list', 'CmdorCtrl+Shift+D'),
      click() {
        activate('delete-list');
      }
    }, {
      label: 'Rename List',
      accelerator: setAcc('rename-list', 'CmdorCtrl+Y'),
      click() {
        activate('rename-list');
      }
    }, {
      type: 'separator'
    }, {
      label: 'Hide Completed Todos',
      accelerator: setAcc('hide-todo', 'CmdorCtrl+Shift+H'),
      click() {
        activate('hide-todo');
      }
    }]
  }, {
    label: 'Todo',
    submenu: [{
      label: 'New Todo',
      accelerator: setAcc('new-todo', 'CmdorCtrl+N'),
      click() {
        activate('new-todo');
      }
    }, {
      label: 'Delete Todo',
      accelerator: setAcc('delete-todo', 'CmdorCtrl+D'),
      click() {
        activate('delete-todo');
      }
    }, {
      label: 'Rename Todo',
      accelerator: setAcc('rename-todo', 'CmdorCtrl+T'),
      click() {
        activate('rename-todo');
      }
    }, {
      type: 'separator'
    }, {
      label: 'Add to My Day',
      accelerator: setAcc('add-my-day', 'CmdorCtrl+K'),
      click() {
        activate('add-my-day');
      }
    }, {
      label: 'Complete Todo',
      accelerator: setAcc('complete-todo', 'CmdorCtrl+Shift+N'),
      click() {
        activate('complete-todo');
      }
    }]
  }, {
    type: 'separator'
  }, {
    label: 'My Day',
    accelerator: setAcc('my-day', 'CmdorCtrl+M'),
    click() {
      activate('my-day');
    }
  }, {
    label: 'Set Reminder',
    accelerator: setAcc('set-reminder', 'CmdorCtrl+Shift+E'),
    click() {
      activate('set-reminder');
    }
  }, {
    label: 'Add Due Date',
    accelerator: setAcc('add-due-date', 'CmdorCtrl+Shift+T'),
    click() {
      activate('add-due-date');
    }
  }, {
    label: 'Toggle Cortana',
    accelerator: setAcc('toggle-cortana', 'CmdorCtrl+E'),
    click() {
      activate('toggle-cortana');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Settings',
    accelerator: setAcc('settings', 'CmdorCtrl+,'),
    click() {
      activate('settings');
    }
  }, {
    label: 'Sign out',
    accelerator: setAcc('sign-out', 'CmdorCtrl+Alt+Q'),
    click() {
      activate('sign-out');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sidebar',
    accelerator: setAcc('toggle-sidebar', 'CmdorCtrl+O'),
    click() {
      activate('toggle-sidebar');
    }
  }, {
    label: 'Return to Todos',
    accelerator: setAcc('return', 'Esc'),
    click() {
      activate('return');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Edit Shortcut Keys',
    accelerator: 'CmdorCtrl+.',
    click() {
      activate('edit-shortcuts');
    }
  }]
}, {
  label: 'Edit',
  submenu: [{
    type: 'separator'
  }, {
    role: 'undo'
  }, {
    role: 'redo'
  }, {
    type: 'separator'
  }, {
    role: 'cut'
  }, {
    role: 'copy'
  }, {
    role: 'paste'
  }, {
    role: 'pasteandmatchstyle'
  }, {
    role: 'delete'
  }, {
    role: 'selectall'
  }]
}, {
  label: 'View',
  submenu: [{
    label: 'Reload',
    accelerator: 'CmdOrCtrl+Shift+R',
    click: (item, focusedWindow) => {
      if (focusedWindow) {
        focusedWindow.reload();
      }
    }
  }, {
    type: 'separator'
  }, {
    label: 'Make Text Larger',
    accelerator: 'CmdOrCtrl+Plus',
    click() {
      activate('zoom-in');
    }
  }, {
    label: 'Make Text Smaller',
    accelerator: 'CmdOrCtrl+-',
    click() {
      activate('zoom-out');
    }
  }, {
    label: 'Reset Zoom Level',
    accelerator: 'CmdOrCtrl+0',
    click() {
      activate('zoom-reset');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sepia Mode',
    accelerator: setAcc('toggle-sepia-mode', 'CmdOrCtrl+G'),
    click() {
      activate('toggle-sepia-mode');
    }
  }, {
    label: 'Toggle Dark Mode',
    accelerator: setAcc('toggle-dark-mode', 'CmdorCtrl+H'),
    click() {
      activate('toggle-dark-mode');
    }
  }, {
    label: 'Toggle Vibrant Mode',
    accelerator: setAcc('toggle-vibrant-mode', 'CmdOrCtrl+Alt+U'),
    click() {
      activate('toggle-vibrant-mode');
    }
  }, {
    label: 'Toggle Vibrant Dark Mode',
    accelerator: setAcc('toggle-vibrant-dark-mode', 'CmdOrCtrl+Alt+J'),
    click() {
      activate('toggle-vibrant-dark-mode');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Navigate to Next List',
    accelerator: 'CmdorCtrl+Tab',
    click() {
      activate('next-list');
    }
  }, {
    label: 'Navigate to Previous List',
    accelerator: 'CmdorCtrl+Shift+Tab',
    click() {
      activate('previous-list');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Full Screen',
    accelerator: 'Ctrl+Command+F',
    click: (item, focusedWindow) => {
      if (focusedWindow) {
        focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
        focusedWindow.send('window:fullscreen', {state: focusedWindow.isFullScreen()});
      }
    }
  }, {
    label: 'Toggle Developer Tools',
    accelerator: 'Alt+Command+I',
    click: (item, focusedWindow) => {
      focusedWindow.toggleDevTools();
    }
  }]
}, {
  role: 'window',
  submenu: [{
    role: 'minimize'
  }, {
    role: 'close'
  }, {
    type: 'separator'
  }, {
    type: 'separator'
  }, {
    role: 'front'
  }, {
    role: 'togglefullscreen'
  }]
}, {
  role: 'help',
  submenu: helpSubmenu
}];

const otherTpl = [{
  label: 'File',
  submenu: [{
    label: 'Search',
    accelerator: 'CmdorCtrl+F',
    click() {
      activate('search');
    }
  }, {
    type: 'separator'
  }, {
    label: 'List',
    submenu: [{
      label: 'New List',
      accelerator: setAcc('new-list', 'CmdorCtrl+L'),
      click() {
        activate('new-list');
      }
    }, {
      label: 'Delete List',
      accelerator: setAcc('delete-list', 'CmdorCtrl+Shift+D'),
      click() {
        activate('delete-list');
      }
    }, {
      label: 'Rename List',
      accelerator: setAcc('rename-list', 'CmdorCtrl+Y'),
      click() {
        activate('rename-list');
      }
    }, {
      type: 'separator'
    }, {
      label: 'Hide Completed Todos',
      accelerator: setAcc('hide-todo', 'CmdorCtrl+Shift+H'),
      click() {
        activate('hide-todo');
      }
    }]
  }, {
    label: 'Todo',
    submenu: [{
      label: 'New Todo',
      accelerator: setAcc('new-todo', 'CmdorCtrl+N'),
      click() {
        activate('new-todo');
      }
    }, {
      label: 'Delete Todo',
      accelerator: setAcc('delete-todo', 'CmdorCtrl+D'),
      click() {
        activate('delete-todo');
      }
    }, {
      label: 'Rename Todo',
      accelerator: setAcc('rename-todo', 'CmdorCtrl+T'),
      click() {
        activate('rename-todo');
      }
    }, {
      type: 'separator'
    }, {
      label: 'Add to My Day',
      accelerator: setAcc('add-my-day', 'CmdorCtrl+K'),
      click() {
        activate('add-my-day');
      }
    }, {
      label: 'Complete Todo',
      accelerator: setAcc('complete-todo', 'CmdorCtrl+Shift+N'),
      click() {
        activate('complete-todo');
      }
    }]
  }, {
    type: 'separator'
  }, {
    label: 'My Day',
    accelerator: setAcc('my-day', 'CmdorCtrl+M'),
    click() {
      activate('my-day');
    }
  }, {
    label: 'Set Reminder',
    accelerator: setAcc('set-reminder', 'CmdorCtrl+Shift+E'),
    click() {
      activate('set-reminder');
    }
  }, {
    label: 'Add Due Date',
    accelerator: setAcc('add-due-date', 'CmdorCtrl+Shift+T'),
    click() {
      activate('add-due-date');
    }
  }, {
    label: 'Toggle Cortana',
    accelerator: setAcc('toggle-cortana', 'CmdorCtrl+E'),
    click() {
      activate('toggle-cortana');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Settings',
    accelerator: setAcc('settings', 'CmdorCtrl+,'),
    click() {
      activate('settings');
    }
  }, {
    label: 'Sign out',
    accelerator: setAcc('sign-out', 'CmdorCtrl+Alt+Q'),
    click() {
      activate('sign-out');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sidebar',
    accelerator: setAcc('toggle-sidebar', 'CmdorCtrl+O'),
    click() {
      activate('toggle-sidebar');
    }
  }, {
    label: 'Return to Todos',
    accelerator: setAcc('return', 'Esc'),
    click() {
      activate('return');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Edit Shortcut Keys',
    accelerator: 'CmdorCtrl+.',
    click() {
      activate('edit-shortcuts');
    }
  }, {
    type: 'separator'
  }, {
    role: 'quit'
  }]
}, {
  label: 'Edit',
  submenu: [{
    role: 'undo'
  }, {
    role: 'redo'
  }, {
    type: 'separator'
  }, {
    role: 'cut'
  }, {
    role: 'copy'
  }, {
    role: 'paste'
  }, {
    role: 'pasteandmatchstyle'
  }, {
    role: 'delete'
  }, {
    type: 'separator'
  }, {
    role: 'selectall'
  }]
}, {
  label: 'View',
  submenu: [{
    label: 'Reload',
    accelerator: 'CmdOrCtrl+Shift+R',
    click: (item, focusedWindow) => {
      if (focusedWindow) {
        focusedWindow.reload();
      }
    }
  }, {
    type: 'separator'
  }, {
    label: 'Make Text Larger',
    accelerator: 'CmdOrCtrl+Plus',
    click() {
      activate('zoom-in');
    }
  }, {
    label: 'Make Text Smaller',
    accelerator: 'CmdOrCtrl+-',
    click() {
      activate('zoom-out');
    }
  }, {
    label: 'Reset Zoom Level',
    accelerator: 'CmdOrCtrl+0',
    click() {
      activate('zoom-reset');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sepia Mode',
    accelerator: setAcc('toggle-sepia-mode', 'CmdOrCtrl+G'),
    click() {
      activate('toggle-sepia-mode');
    }
  }, {
    label: 'Toggle Dark Mode',
    accelerator: setAcc('toggle-dark-mode', 'CmdorCtrl+H'),
    click() {
      activate('toggle-dark-mode');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Navigate to Next List',
    accelerator: 'CmdorCtrl+Tab',
    click() {
      activate('next-list');
    }
  }, {
    label: 'Navigate to Previous List',
    accelerator: 'CmdorCtrl+Shift+Tab',
    click() {
      activate('previous-list');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Full Screen',
    accelerator: 'F11',
    click: (item, focusedWindow) => {
      if (focusedWindow) {
        focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
        focusedWindow.send('window:fullscreen', {state: focusedWindow.isFullScreen()});
      }
    }
  }, {
    label: 'Toggle Developer Tools',
    accelerator: 'Ctrl+Shift+I',
    click: (item, focusedWindow) => {
      focusedWindow.toggleDevTools();
    }
  }]
}, {
  role: 'help',
  submenu: helpSubmenu
}];

const tpl = process.platform === 'darwin' ? darwinTpl : otherTpl;

module.exports = electron.Menu.buildFromTemplate(tpl);
<commit_msg>Homepage URL update<commit_after>'use strict';
const path = require('path');
const electron = require('electron');
const os = require('os');
const fs = require('fs-extra');

const join = path.join;
const app = electron.app;
const shell = electron.shell;
const appName = app.getName();
const BrowserWindow = electron.BrowserWindow;

let configData;
const oaJSON = '.ao.json'; // Config file name
const homeDir = os.homedir();
const homeConfig = join(homeDir, oaJSON); // Config file on home directory
const defaultConfig = join(__dirname, '.', oaJSON); // Default config file directory

const sourceURL = 'https://github.com/klauscfhq/ao';
const homepageURL = 'https://klauscfhq.github.io/ao';
const issueURL = 'https://github.com/klauscfhq/ao/issues/new';
const releaseURL = 'https://github.com/klauscfhq/ao/releases/latest';

function getConfig() {
  // Create a new default config file
  // if it does not already exist
  if (!fs.existsSync(homeConfig)) {
    try {
      fs.copySync(defaultConfig, homeConfig);
      console.log('Ao config file was created successfully');
    } catch (err) {
      console.error(err);
    }
  }
  // Parse the content of the config file
  try {
    configData = JSON.parse(fs.readFileSync(homeConfig, 'utf8'));
  } catch (err) {
    console.log('Invalid JSON object');
  }
  return configData;
}

// Get the user-defined settings
const aoConfig = getConfig();

function setAcc(custom, predifined) {
  // Return the custom or predifined shortcut keys
  if (Object.prototype.hasOwnProperty.call(aoConfig.shortcutKeys, custom)) {
    return aoConfig.shortcutKeys[custom];
  }
  return predifined;
}

function activate(custom) {
  const appWindow = BrowserWindow.getAllWindows()[0];
  // Extra measure in order to be shown
  appWindow.show();
  appWindow.webContents.send(custom);
}

const helpSubmenu = [{
  label: `Ao Homepage`,
  click() {
    shell.openExternal(homepageURL);
  }
}, {
  type: 'separator'
}, {
  label: 'Fork Source',
  click() {
    shell.openExternal(sourceURL);
  }
}, {
  label: `Report Issue`,
  click() {
    shell.openExternal(issueURL);
  }
}, {
  label: `Latest Release`,
  click() {
    shell.openExternal(releaseURL);
  }
}];

if (process.platform !== 'darwin') {
  helpSubmenu.push({
    type: 'separator'
  }, {
    role: 'about',
    click() {
      electron.dialog.showMessageBox({
        title: `About Ao`,
        message: `Ao ${app.getVersion()}`,
        detail: 'Coded with love by Klaus Sinani',
        icon: path.join(__dirname, 'static/Icon.png'),
        buttons: []
      });
    }
  });
}

const darwinTpl = [{
  label: appName,
  submenu: [{
    role: 'about'
  }, {
    type: 'separator'
  }, {
    role: 'services',
    submenu: []
  }, {
    type: 'separator'
  }, {
    role: 'hide'
  }, {
    role: 'hideothers'
  }, {
    role: 'unhide'
  }, {
    type: 'separator'
  }, {
    role: 'quit'
  }]
}, {
  label: 'File',
  submenu: [{
    label: 'Search',
    accelerator: 'CmdorCtrl+F',
    click() {
      activate('search');
    }
  }, {
    type: 'separator'
  }, {
    label: 'List',
    submenu: [{
      label: 'New List',
      accelerator: setAcc('new-list', 'CmdorCtrl+L'),
      click() {
        activate('new-list');
      }
    }, {
      label: 'Delete List',
      accelerator: setAcc('delete-list', 'CmdorCtrl+Shift+D'),
      click() {
        activate('delete-list');
      }
    }, {
      label: 'Rename List',
      accelerator: setAcc('rename-list', 'CmdorCtrl+Y'),
      click() {
        activate('rename-list');
      }
    }, {
      type: 'separator'
    }, {
      label: 'Hide Completed Todos',
      accelerator: setAcc('hide-todo', 'CmdorCtrl+Shift+H'),
      click() {
        activate('hide-todo');
      }
    }]
  }, {
    label: 'Todo',
    submenu: [{
      label: 'New Todo',
      accelerator: setAcc('new-todo', 'CmdorCtrl+N'),
      click() {
        activate('new-todo');
      }
    }, {
      label: 'Delete Todo',
      accelerator: setAcc('delete-todo', 'CmdorCtrl+D'),
      click() {
        activate('delete-todo');
      }
    }, {
      label: 'Rename Todo',
      accelerator: setAcc('rename-todo', 'CmdorCtrl+T'),
      click() {
        activate('rename-todo');
      }
    }, {
      type: 'separator'
    }, {
      label: 'Add to My Day',
      accelerator: setAcc('add-my-day', 'CmdorCtrl+K'),
      click() {
        activate('add-my-day');
      }
    }, {
      label: 'Complete Todo',
      accelerator: setAcc('complete-todo', 'CmdorCtrl+Shift+N'),
      click() {
        activate('complete-todo');
      }
    }]
  }, {
    type: 'separator'
  }, {
    label: 'My Day',
    accelerator: setAcc('my-day', 'CmdorCtrl+M'),
    click() {
      activate('my-day');
    }
  }, {
    label: 'Set Reminder',
    accelerator: setAcc('set-reminder', 'CmdorCtrl+Shift+E'),
    click() {
      activate('set-reminder');
    }
  }, {
    label: 'Add Due Date',
    accelerator: setAcc('add-due-date', 'CmdorCtrl+Shift+T'),
    click() {
      activate('add-due-date');
    }
  }, {
    label: 'Toggle Cortana',
    accelerator: setAcc('toggle-cortana', 'CmdorCtrl+E'),
    click() {
      activate('toggle-cortana');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Settings',
    accelerator: setAcc('settings', 'CmdorCtrl+,'),
    click() {
      activate('settings');
    }
  }, {
    label: 'Sign out',
    accelerator: setAcc('sign-out', 'CmdorCtrl+Alt+Q'),
    click() {
      activate('sign-out');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sidebar',
    accelerator: setAcc('toggle-sidebar', 'CmdorCtrl+O'),
    click() {
      activate('toggle-sidebar');
    }
  }, {
    label: 'Return to Todos',
    accelerator: setAcc('return', 'Esc'),
    click() {
      activate('return');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Edit Shortcut Keys',
    accelerator: 'CmdorCtrl+.',
    click() {
      activate('edit-shortcuts');
    }
  }]
}, {
  label: 'Edit',
  submenu: [{
    type: 'separator'
  }, {
    role: 'undo'
  }, {
    role: 'redo'
  }, {
    type: 'separator'
  }, {
    role: 'cut'
  }, {
    role: 'copy'
  }, {
    role: 'paste'
  }, {
    role: 'pasteandmatchstyle'
  }, {
    role: 'delete'
  }, {
    role: 'selectall'
  }]
}, {
  label: 'View',
  submenu: [{
    label: 'Reload',
    accelerator: 'CmdOrCtrl+Shift+R',
    click: (item, focusedWindow) => {
      if (focusedWindow) {
        focusedWindow.reload();
      }
    }
  }, {
    type: 'separator'
  }, {
    label: 'Make Text Larger',
    accelerator: 'CmdOrCtrl+Plus',
    click() {
      activate('zoom-in');
    }
  }, {
    label: 'Make Text Smaller',
    accelerator: 'CmdOrCtrl+-',
    click() {
      activate('zoom-out');
    }
  }, {
    label: 'Reset Zoom Level',
    accelerator: 'CmdOrCtrl+0',
    click() {
      activate('zoom-reset');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sepia Mode',
    accelerator: setAcc('toggle-sepia-mode', 'CmdOrCtrl+G'),
    click() {
      activate('toggle-sepia-mode');
    }
  }, {
    label: 'Toggle Dark Mode',
    accelerator: setAcc('toggle-dark-mode', 'CmdorCtrl+H'),
    click() {
      activate('toggle-dark-mode');
    }
  }, {
    label: 'Toggle Vibrant Mode',
    accelerator: setAcc('toggle-vibrant-mode', 'CmdOrCtrl+Alt+U'),
    click() {
      activate('toggle-vibrant-mode');
    }
  }, {
    label: 'Toggle Vibrant Dark Mode',
    accelerator: setAcc('toggle-vibrant-dark-mode', 'CmdOrCtrl+Alt+J'),
    click() {
      activate('toggle-vibrant-dark-mode');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Navigate to Next List',
    accelerator: 'CmdorCtrl+Tab',
    click() {
      activate('next-list');
    }
  }, {
    label: 'Navigate to Previous List',
    accelerator: 'CmdorCtrl+Shift+Tab',
    click() {
      activate('previous-list');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Full Screen',
    accelerator: 'Ctrl+Command+F',
    click: (item, focusedWindow) => {
      if (focusedWindow) {
        focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
        focusedWindow.send('window:fullscreen', {state: focusedWindow.isFullScreen()});
      }
    }
  }, {
    label: 'Toggle Developer Tools',
    accelerator: 'Alt+Command+I',
    click: (item, focusedWindow) => {
      focusedWindow.toggleDevTools();
    }
  }]
}, {
  role: 'window',
  submenu: [{
    role: 'minimize'
  }, {
    role: 'close'
  }, {
    type: 'separator'
  }, {
    type: 'separator'
  }, {
    role: 'front'
  }, {
    role: 'togglefullscreen'
  }]
}, {
  role: 'help',
  submenu: helpSubmenu
}];

const otherTpl = [{
  label: 'File',
  submenu: [{
    label: 'Search',
    accelerator: 'CmdorCtrl+F',
    click() {
      activate('search');
    }
  }, {
    type: 'separator'
  }, {
    label: 'List',
    submenu: [{
      label: 'New List',
      accelerator: setAcc('new-list', 'CmdorCtrl+L'),
      click() {
        activate('new-list');
      }
    }, {
      label: 'Delete List',
      accelerator: setAcc('delete-list', 'CmdorCtrl+Shift+D'),
      click() {
        activate('delete-list');
      }
    }, {
      label: 'Rename List',
      accelerator: setAcc('rename-list', 'CmdorCtrl+Y'),
      click() {
        activate('rename-list');
      }
    }, {
      type: 'separator'
    }, {
      label: 'Hide Completed Todos',
      accelerator: setAcc('hide-todo', 'CmdorCtrl+Shift+H'),
      click() {
        activate('hide-todo');
      }
    }]
  }, {
    label: 'Todo',
    submenu: [{
      label: 'New Todo',
      accelerator: setAcc('new-todo', 'CmdorCtrl+N'),
      click() {
        activate('new-todo');
      }
    }, {
      label: 'Delete Todo',
      accelerator: setAcc('delete-todo', 'CmdorCtrl+D'),
      click() {
        activate('delete-todo');
      }
    }, {
      label: 'Rename Todo',
      accelerator: setAcc('rename-todo', 'CmdorCtrl+T'),
      click() {
        activate('rename-todo');
      }
    }, {
      type: 'separator'
    }, {
      label: 'Add to My Day',
      accelerator: setAcc('add-my-day', 'CmdorCtrl+K'),
      click() {
        activate('add-my-day');
      }
    }, {
      label: 'Complete Todo',
      accelerator: setAcc('complete-todo', 'CmdorCtrl+Shift+N'),
      click() {
        activate('complete-todo');
      }
    }]
  }, {
    type: 'separator'
  }, {
    label: 'My Day',
    accelerator: setAcc('my-day', 'CmdorCtrl+M'),
    click() {
      activate('my-day');
    }
  }, {
    label: 'Set Reminder',
    accelerator: setAcc('set-reminder', 'CmdorCtrl+Shift+E'),
    click() {
      activate('set-reminder');
    }
  }, {
    label: 'Add Due Date',
    accelerator: setAcc('add-due-date', 'CmdorCtrl+Shift+T'),
    click() {
      activate('add-due-date');
    }
  }, {
    label: 'Toggle Cortana',
    accelerator: setAcc('toggle-cortana', 'CmdorCtrl+E'),
    click() {
      activate('toggle-cortana');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Settings',
    accelerator: setAcc('settings', 'CmdorCtrl+,'),
    click() {
      activate('settings');
    }
  }, {
    label: 'Sign out',
    accelerator: setAcc('sign-out', 'CmdorCtrl+Alt+Q'),
    click() {
      activate('sign-out');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sidebar',
    accelerator: setAcc('toggle-sidebar', 'CmdorCtrl+O'),
    click() {
      activate('toggle-sidebar');
    }
  }, {
    label: 'Return to Todos',
    accelerator: setAcc('return', 'Esc'),
    click() {
      activate('return');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Edit Shortcut Keys',
    accelerator: 'CmdorCtrl+.',
    click() {
      activate('edit-shortcuts');
    }
  }, {
    type: 'separator'
  }, {
    role: 'quit'
  }]
}, {
  label: 'Edit',
  submenu: [{
    role: 'undo'
  }, {
    role: 'redo'
  }, {
    type: 'separator'
  }, {
    role: 'cut'
  }, {
    role: 'copy'
  }, {
    role: 'paste'
  }, {
    role: 'pasteandmatchstyle'
  }, {
    role: 'delete'
  }, {
    type: 'separator'
  }, {
    role: 'selectall'
  }]
}, {
  label: 'View',
  submenu: [{
    label: 'Reload',
    accelerator: 'CmdOrCtrl+Shift+R',
    click: (item, focusedWindow) => {
      if (focusedWindow) {
        focusedWindow.reload();
      }
    }
  }, {
    type: 'separator'
  }, {
    label: 'Make Text Larger',
    accelerator: 'CmdOrCtrl+Plus',
    click() {
      activate('zoom-in');
    }
  }, {
    label: 'Make Text Smaller',
    accelerator: 'CmdOrCtrl+-',
    click() {
      activate('zoom-out');
    }
  }, {
    label: 'Reset Zoom Level',
    accelerator: 'CmdOrCtrl+0',
    click() {
      activate('zoom-reset');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sepia Mode',
    accelerator: setAcc('toggle-sepia-mode', 'CmdOrCtrl+G'),
    click() {
      activate('toggle-sepia-mode');
    }
  }, {
    label: 'Toggle Dark Mode',
    accelerator: setAcc('toggle-dark-mode', 'CmdorCtrl+H'),
    click() {
      activate('toggle-dark-mode');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Navigate to Next List',
    accelerator: 'CmdorCtrl+Tab',
    click() {
      activate('next-list');
    }
  }, {
    label: 'Navigate to Previous List',
    accelerator: 'CmdorCtrl+Shift+Tab',
    click() {
      activate('previous-list');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Full Screen',
    accelerator: 'F11',
    click: (item, focusedWindow) => {
      if (focusedWindow) {
        focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
        focusedWindow.send('window:fullscreen', {state: focusedWindow.isFullScreen()});
      }
    }
  }, {
    label: 'Toggle Developer Tools',
    accelerator: 'Ctrl+Shift+I',
    click: (item, focusedWindow) => {
      focusedWindow.toggleDevTools();
    }
  }]
}, {
  role: 'help',
  submenu: helpSubmenu
}];

const tpl = process.platform === 'darwin' ? darwinTpl : otherTpl;

module.exports = electron.Menu.buildFromTemplate(tpl);
<|endoftext|>


<commit_before>var process = require('process')
var fs = require('fs')
var nearley = require('nearley')
var grammar = require('./grammar')
var helper = require('./helper')

var filename = process.argv[2]
var data = fs.readFileSync(filename, 'utf8')

// Normalize endings

console.error('Normalize endings...')
data = data.replace(/\r\n/g, '\n').replace(/\r/g, '')

// Remove comments & strings

console.error('Remove comments...')

var removed = helper.removeStringComments(data)
var pureCode = removed[1]
var comments = removed[2]
data = removed[0]
console.log(comments)

// Get indent length

console.error('Determine indent length...')
var indentLength

try {
    indentLength = helper.getIndent(data)
} catch(e) {
    console.error('Mixed indentation @' + e.line + ':1')
    return
}

// Indentifizer

console.error('Indentifizer...')
data = helper.indentifizer(data, pureCode, indentLength)

// Invoke nearley

console.error('Parsing...')
var p = new nearley.Parser(grammar.ParserRules, grammar.ParserStart)

try {
    p.feed(data)

    if (!p.results || !p.results.length)
        throw { message: 'Unexpected end', offset: data.length - 1 }
} catch(e) {
    var pos = helper.offsetToLinePos(e.offset, data)
    var message = e.message
    if (message.indexOf('nearley') != -1) message = 'Parse error'

    console.error(message + ' @' + pos[0] + ':' + pos[1])
    return
}

console.error('Compiled.')
console.error(p.results.length == 1 ? 'No ambiguity detected.' : 'Ambiguity detected.')
console.dir(p.results, { depth: null })
<commit_msg>Update milk.js<commit_after>var process = require('process')
var fs = require('fs')
var nearley = require('nearley')
var grammar = require('./grammar')
var helper = require('./helper')
var translate = require('./translate')

var filename = process.argv[2]
var data = fs.readFileSync(filename, 'utf8')

// Normalize endings

console.error('Normalize endings...')
data = data.replace(/\r\n/g, '\n').replace(/\r/g, '')

// Remove comments & strings

console.error('Remove comments...')

var removed = helper.removeStringComments(data)
var pureCode = removed[1]
var comments = removed[2]
data = removed[0]

// Get indent length

console.error('Determine indent length...')
var indentLength

try {
    indentLength = helper.getIndent(data)
} catch(e) {
    console.error('Mixed indentation @' + e.line + ':1')
    return
}

// Indentifizer

console.error('Indentifizer...')
data = helper.indentifizer(data, pureCode, indentLength)

// Invoke nearley

console.error('Parsing...')
var p = new nearley.Parser(grammar.ParserRules, grammar.ParserStart)

try {
    p.feed(data)

    if (!p.results || !p.results.length)
        throw { message: 'Unexpected end', offset: data.length - 1 }
} catch(e) {
    var pos = helper.offsetToLinePos(e.offset, data)
    var message = e.message
    if (message.indexOf('nearley') != -1) message = 'Parse error'

    console.error(message + ' @' + pos[0] + ':' + pos[1])
    return
}

console.error('Compiled.')
console.error(p.results.length == 1 ? 'No ambiguity detected.' : 'Ambiguity detected.')

var tree = p.results[0]
console.dir(tree, { depth: null })

// Translating

console.error('Translating...')
console.log(translate(tree))
<|endoftext|>


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

function Mime() {
  // Map of extension to mime type
  this.types = Object.create(null);

  // Map of mime type to extension
  this.extensions = Object.create(null);
}

/**
 * Define mimetype -> extension mappings.  Each key is a mime-type that maps
 * to an array of extensions associated with the type.  The first extension is
 * used as the default extension for the type.
 *
 * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']});
 *
 * @param map (Object) type definitions
 */
Mime.prototype.define = function (map) {
  for (var type in map) {
    var exts = map[type];

    for (var i = 0; i < exts.length; i++) {
      this.types[exts[i]] = type;
    }

    // Default extension is the first one we encounter
    if (!this.extensions[type]) {
      this.extensions[type] = exts[0];
    }
  }
};

/**
 * Load an Apache2-style ".types" file
 *
 * This may be called multiple times (it's expected).  Where files declare
 * overlapping types/extensions, the last file wins.
 *
 * @param file (String) path of file to load.
 */
Mime.prototype.load = function(file) {
  // Read file and split into lines
  var map = {},
      content = fs.readFileSync(file, 'ascii'),
      lines = content.split(/[\r\n]+/);

  lines.forEach(function(line, lineno) {
    // Clean up whitespace/comments, and split into fields
    var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/);
    map[fields.shift()] = fields;
  });

  this.define(map);
};

/**
 * Lookup a mime type based on extension
 */
Mime.prototype.lookup = function(path, fallback) {
  var ext = path.replace(/.*[\.\/]/, '').toLowerCase();

  return this.types[ext] || fallback || this.default_type;
};

/**
 * Return file extension associated with a mime type
 */
Mime.prototype.extension = function(mimeType) {
  return this.extensions[mimeType];
};

/**
 * Lookup a charset based on mime type.
 */
Mime.prototype.charsets = {
  lookup: function (mimeType, fallback) {
    // Assume text types are utf8.  Modify mime logic as needed.
    return (/^text\//).test(mimeType) ? 'UTF-8' : fallback;
  }
};

module.exports = new Mime();
module.exports.Mime = Mime;

// Load our local copy of
// http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
module.exports.load(path.join(__dirname, 'types/mime.types'));

// Overlay enhancements submitted by the node.js community
module.exports.load(path.join(__dirname, 'types/node.types'));

// Set the default type
module.exports.default_type = module.exports.types.bin;
<commit_msg>Code cleanup.  Make charsets.lookup specific to default Mime instance.<commit_after>var path = require('path');
var fs = require('fs');

function Mime() {
  // Map of extension -> mime type
  this.types = Object.create(null);

  // Map of mime type -> extension
  this.extensions = Object.create(null);
}

/**
 * Define mimetype -> extension mappings.  Each key is a mime-type that maps
 * to an array of extensions associated with the type.  The first extension is
 * used as the default extension for the type.
 *
 * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']});
 *
 * @param map (Object) type definitions
 */
Mime.prototype.define = function (map) {
  for (var type in map) {
    var exts = map[type];

    for (var i = 0; i < exts.length; i++) {
      this.types[exts[i]] = type;
    }

    // Default extension is the first one we encounter
    if (!this.extensions[type]) {
      this.extensions[type] = exts[0];
    }
  }
};

/**
 * Load an Apache2-style ".types" file
 *
 * This may be called multiple times (it's expected).  Where files declare
 * overlapping types/extensions, the last file wins.
 *
 * @param file (String) path of file to load.
 */
Mime.prototype.load = function(file) {
  // Read file and split into lines
  var map = {},
      content = fs.readFileSync(file, 'ascii'),
      lines = content.split(/[\r\n]+/);

  lines.forEach(function(line) {
    // Clean up whitespace/comments, and split into fields
    var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/);
    map[fields.shift()] = fields;
  });

  this.define(map);
};

/**
 * Lookup a mime type based on extension
 */
Mime.prototype.lookup = function(path, fallback) {
  var ext = path.replace(/.*[\.\/]/, '').toLowerCase();

  return this.types[ext] || fallback || this.default_type;
};

/**
 * Return file extension associated with a mime type
 */
Mime.prototype.extension = function(mimeType) {
  return this.extensions[mimeType];
};

// Default instance
var mime = new Mime();

// Load local copy of
// http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
mime.load(path.join(__dirname, 'types/mime.types'));

// Load additional types from node.js community
mime.load(path.join(__dirname, 'types/node.types'));

// Default type
mime.default_type = mime.lookup('bin');

//
// Additional API specific to the default instance
//

mime.Mime = Mime;

/**
 * Lookup a charset based on mime type.
 */
mime.charsets = {
  lookup: function(mimeType, fallback) {
    // Assume text types are utf8
    return (/^text\//).test(mimeType) ? 'UTF-8' : fallback;
  }
}

module.exports = mime;
<|endoftext|>


<commit_before>var etg = require("./etg");
var ui = require("./uiutil");
var sock = require("./sock");
var Cards = require("./Cards");
var etgutil = require("./etgutil");
exports.mkPremade = function(name, daily) {
	return function() {
		var urdeck = sock.getDeck();
		if (etgutil.decklength(urdeck) < (sock.user ? 31 : 11)) {
			require("./views/Editor")();
			return;
		}
		var cost = daily !== undefined ? 0 : name == "mage" ? 5 : 20, foedata;
		if (sock.user) {
			if (daily === undefined){
				if (sock.user.gold < cost) {
					chat("Requires " + cost + "\u00A4");
					return;
				}
			}else{
				foedata = exports[name][sock.user[name == "mage" ? "dailymage" : "dailydg"]];
			}
		}
		if (!foedata) foedata = exports.giveRandom(name);
		var foename = name[0].toUpperCase() + name.slice(1) + "\n" + foedata[0];
		var gameData = { deck: foedata[1], urdeck: urdeck, seed: Math.random() * etgutil.MAX_INT, foename: foename };
		if (name == "mage"){
			gameData.p2hp = 125;
		}else{
			gameData.p2hp = 200;
			gameData.p2markpower = 3;
			gameData.p2drawpower = 2;
		}
		if (!sock.user) ui.parsepvpstats(gameData);
		else gameData.cost = cost;
		gameData.level = name == "mage" ? 1 : 3;
		if (daily !== undefined) gameData.daily = daily;
		return require("./views/Match")(gameData, true);
	}
}
exports.mkAi = function(level, daily) {
	return function() {
		if (Cards.loaded){
			var urdeck = sock.getDeck();
			if (etgutil.decklength(urdeck) < (sock.user ? 31 : 11)) {
				require("./views/Editor")();
				return;
			}
			var cost = daily !== undefined || level == 0 ? 0 : level == 1 ? 5 : 10;
			if (sock.user && cost) {
				if (sock.user.gold < cost) {
					chat("Requires " + cost + "\u00A4");
					return;
				}
			}
			var deck = require("./ai/deck")(level);
			aideck.value = deck;

			var randomNames = [
				"Adrienne", "Audrie",
				"Billie", "Brendon",
				"Charles", "Caddy",
				"Dane", "Digna",
				"Emory", "Evan",
				"Fern",
				"Garland", "Gord",
				"Margie", "Mariah", "Martina", "Monroe", "Murray",
				"Page", "Pariah",
				"Rocky", "Ronald", "Ren",
				"Seth", "Sherman", "Stormy",
				"Tammi",
				"Yuriko"
			];
			var typeName = ["Commoner", "Mage", "Champion"];

			var foename = typeName[level] + "\n" + randomNames[etg.PlayerRng.upto(randomNames.length)];
			var gameData = { deck: deck, urdeck: urdeck, seed: Math.random() * etgutil.MAX_INT, p2hp: level == 0 ? 100 : level == 1 ? 125 : 150, p2markpower: level == 2 ? 2 : 1, foename: foename, p2drawpower: level == 2 ? 2 : 1 };
			if (!sock.user) ui.parsepvpstats(gameData);
			else gameData.cost = cost;
			gameData.level = level;
			if (daily !== undefined) gameData.daily = daily;
			return require("./views/Match")(gameData, true);
		}
	}
}<commit_msg>Fix mkAi.js<commit_after>var etg = require("./etg");
var ui = require("./uiutil");
var sock = require("./sock");
var Cards = require("./Cards");
var aiDecks = require("./Decks");
var etgutil = require("./etgutil");
exports.mkPremade = function(name, daily) {
	return function() {
		var urdeck = sock.getDeck();
		if (etgutil.decklength(urdeck) < (sock.user ? 31 : 11)) {
			require("./views/Editor")();
			return;
		}
		var cost = daily !== undefined ? 0 : name == "mage" ? 5 : 20, foedata;
		if (sock.user) {
			if (daily === undefined){
				if (sock.user.gold < cost) {
					chat("Requires " + cost + "\u00A4");
					return;
				}
			}else{
				foedata = aiDecks[name][sock.user[name == "mage" ? "dailymage" : "dailydg"]];
			}
		}
		if (!foedata) foedata = aiDecks.giveRandom(name);
		var foename = name[0].toUpperCase() + name.slice(1) + "\n" + foedata[0];
		var gameData = { deck: foedata[1], urdeck: urdeck, seed: Math.random() * etgutil.MAX_INT, foename: foename };
		if (name == "mage"){
			gameData.p2hp = 125;
		}else{
			gameData.p2hp = 200;
			gameData.p2markpower = 3;
			gameData.p2drawpower = 2;
		}
		if (!sock.user) ui.parsepvpstats(gameData);
		else gameData.cost = cost;
		gameData.level = name == "mage" ? 1 : 3;
		if (daily !== undefined) gameData.daily = daily;
		return require("./views/Match")(gameData, true);
	}
}
exports.mkAi = function(level, daily) {
	return function() {
		if (Cards.loaded){
			var urdeck = sock.getDeck();
			if (etgutil.decklength(urdeck) < (sock.user ? 31 : 11)) {
				require("./views/Editor")();
				return;
			}
			var cost = daily !== undefined || level == 0 ? 0 : level == 1 ? 5 : 10;
			if (sock.user && cost) {
				if (sock.user.gold < cost) {
					chat("Requires " + cost + "\u00A4");
					return;
				}
			}
			var deck = require("./ai/deck")(level);
			aideck.value = deck;

			var randomNames = [
				"Adrienne", "Audrie",
				"Billie", "Brendon",
				"Charles", "Caddy",
				"Dane", "Digna",
				"Emory", "Evan",
				"Fern",
				"Garland", "Gord",
				"Margie", "Mariah", "Martina", "Monroe", "Murray",
				"Page", "Pariah",
				"Rocky", "Ronald", "Ren",
				"Seth", "Sherman", "Stormy",
				"Tammi",
				"Yuriko"
			];
			var typeName = ["Commoner", "Mage", "Champion"];

			var foename = typeName[level] + "\n" + randomNames[etg.PlayerRng.upto(randomNames.length)];
			var gameData = { deck: deck, urdeck: urdeck, seed: Math.random() * etgutil.MAX_INT, p2hp: level == 0 ? 100 : level == 1 ? 125 : 150, p2markpower: level == 2 ? 2 : 1, foename: foename, p2drawpower: level == 2 ? 2 : 1 };
			if (!sock.user) ui.parsepvpstats(gameData);
			else gameData.cost = cost;
			gameData.level = level;
			if (daily !== undefined) gameData.daily = daily;
			return require("./views/Match")(gameData, true);
		}
	}
}<|endoftext|>