<commit_before>'use strict';

(function() {
  const DEFAULT_FETCH_INTERVAL_SEC = 600;
  const NOTIE_DISPLAY_SEC = 1.5;
  const COLOR_ICON_FILENAME_64 = 'redmine_icon_color_64.png';
  const BLACK_ICON_FILENAME_24 = 'redmine_icon_black_24.png';
  const BLACK_ICON_FILENAME_24_NOTIFICATION = 'redmine_icon_black_24_notification.png';
  const COLOR_ICON_FILENAME_24 = 'redmine_icon_color_24.png';
  const COLOR_ICON_FILENAME_24_NOTIFICATION = 'redmine_icon_color_24_notification.png';

  var remote = window.require('remote');
  var shell = remote.require('shell');
  var Menu = remote.require('menu');
  var Tray = remote.require('tray');
  var fs = require('fs');
  var notie = require('notie');
  var notifier = require('node-notifier');

  var FETCH_MODE = Object.freeze({ TIME: 'TIME', DATE: 'DATE' });

  /**
   * Initialize the RedmineNotifier object.
   * @constructor
   */
  function RedmineNotifier() {
    this._tray = null;
    this._lastExecutionTime = null;
    this._settings = null;
    this._fetchTimer = null;
    this._fetchMode = null;

    if (process.platform === 'darwin') {
      this._iconFilePath             = __dirname + '/images/' + BLACK_ICON_FILENAME_24;
      this._notificationIconFilePath = __dirname + '/images/' + BLACK_ICON_FILENAME_24_NOTIFICATION;
    } else {
      this._iconFilePath             = __dirname + '/images/' + COLOR_ICON_FILENAME_24;
      this._notificationIconFilePath = __dirname + '/images/' + COLOR_ICON_FILENAME_24_NOTIFICATION;
    }
  }

  /**
   * Initialize the application menu and context menu.
   */
  RedmineNotifier.prototype.initMenu = function() {
    var _this = this;

    var appMenu = Menu.buildFromTemplate([
      {
        label: 'Edit',
        submenu: [
          { label: 'Undo',       accelerator: 'CmdOrCtrl+Z',       role: 'undo' },
          { label: 'Redo',       accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
          { 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' }
        ]
      }
    ]);

    var contextMenu = Menu.buildFromTemplate([
      {
        label: 'Preferences',
        click: function() {
          remote.getCurrentWindow().show();
        }
      }, {
        label: 'Quit',
        click: function() {
          remote.app.quit();
        }
      }
    ]);

    Menu.setApplicationMenu(appMenu);

    _this._tray = new Tray(_this._iconFilePath);
    _this._tray.setContextMenu(contextMenu);

    return this;
  };

  /**
   * Initialize the event listeners.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.initEventListener = function() {
    var _this = this;

    document.getElementById('save-button').addEventListener('click', function() {
      _this.readScreenSettings();

      if (_this.validateSettings()) {
        _this.initFetch()
          .updateSettings();
      } else {
        _this.readStoredSettings();
      }

      notie.alert(1, 'Settings have been saved.', NOTIE_DISPLAY_SEC);
    });

    document.getElementById('close-button').addEventListener('click', function() {
      _this.readStoredSettings()
        .displaySettings();
      remote.getCurrentWindow().hide();
    });

    document.getElementById('test-connection-button').addEventListener('click', function() {
      _this.testConnection(FETCH_MODE.TIME);
    });

    return this;
  };

  /**
   * Display the default settings on the screen.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.displayDefaultSettings = function() {
    document.getElementById('default-fetch-interval-sec').innerHTML = DEFAULT_FETCH_INTERVAL_SEC;
    return this;
  };

  /**
   * Display the settings on the screen.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.displaySettings = function() {
    document.getElementById('url').value = this._settings.url;
    document.getElementById('api-key').value = this._settings.apiKey;
    document.getElementById('project-id').value = this._settings.projectId;
    document.getElementById('fetch-interval-sec').value = this._settings.fetchIntervalSec;
    return this;
  };

  /**
   * Get the settings from the screen.
   * @return {Object} Settings.
   */
  RedmineNotifier.prototype.getPageSettings = function() {
    return {
      url: document.getElementById('url').value,
      apiKey: document.getElementById('api-key').value,
      projectId: document.getElementById('project-id').value,
      fetchIntervalSec: document.getElementById('fetch-interval-sec').value
    };
  };

  /**
   * Read the settings from the screen.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.readScreenSettings = function() {
    this._settings = this.getPageSettings();
    return this;
  };

  /**
   * Read the settings from the localStorage.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.readStoredSettings = function() {
    this._settings = {
      url: localStorage.getItem('url'),
      apiKey: localStorage.getItem('apiKey'),
      projectId: localStorage.getItem('projectId'),
      fetchIntervalSec: localStorage.getItem('fetchIntervalSec')
    };

    return this;
  };

  /**
   * Update the stored last execution time.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.updateLastExecutionTime = function() {
    this._lastExecutionTime = (new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
    localStorage.setItem('lastExecutionTime', this._lastExecutionTime);
    return this;
  };

  /**
   * Update the stored settings.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.updateSettings = function() {
    localStorage.setItem('url', this._settings.url);
    localStorage.setItem('apiKey', this._settings.apiKey);
    localStorage.setItem('projectId', this._settings.projectId);
    localStorage.setItem('fetchIntervalSec', this._settings.fetchIntervalSec);
    return this;
  };

  /**
   * Validate the settings.
   * @return {boolean} true if valid.
   */
  RedmineNotifier.prototype.validateSettings = function() {
    if (this._settings.url && this._settings.apiKey) {
      return true;
    } else {
      notie.alert(3, 'Please enter required fields.', NOTIE_DISPLAY_SEC);
      return false;
    }
  };

  /**
   * Initialize the fetch function.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.initFetch = function() {
    var _this = this;
    var intervalMsec = 1000 * (this._settings.fetchIntervalSec || DEFAULT_FETCH_INTERVAL_SEC);

    clearInterval(this._fetchTimer);

    this._fetchTimer = setInterval(function() {
      _this.fetch(_this._fetchMode || FETCH_MODE.TIME);
    }, intervalMsec);

    return this;
  };

  /**
   * Fetch updated issues by using Redmine REST API.
   * @param {string} mode - Time or date.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.fetch = function(mode) {
    var _this = this;
    var xhr = new XMLHttpRequest();

    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        _this.handleResponseFetch(mode, xhr.status, xhr.responseText);
      }
    };

    xhr.open('GET', this._settings.url + '/issues.json' + this.getRequestParams(mode, this._settings.projectId));
    xhr.setRequestHeader('X-Redmine-API-Key', this._settings.apiKey);
    xhr.send();

    _this._tray.setImage(_this._iconFilePath);

    return this;
  };

  /**
   * Handle the response for the fetch.
   * @param {string} mode - Time or date.
   * @param {number} status - Response status.
   * @param {string} responseText - Response text.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.handleResponseFetch = function(mode, status, responseText) {
    if (mode === FETCH_MODE.TIME) {
      if (status === 200) {
        this.notify(JSON.parse(responseText).issues)
          .updateLastExecutionTime();
      } else if (status === 422) {
        // Retry with date mode if Redmine API doesn't accept time format
        this._fetchMode = FETCH_MODE.DATE;
        this.fetch(FETCH_MODE.DATE);
      }
    } else {
      if (status === 200) {
        this.notify(this.pickIssues(JSON.parse(responseText).issues));
      }

      this.updateLastExecutionTime();
    }

    return this;
  };

  /**
   * Get issues which were updated after last execution time.
   * @param {string} responseIssues - Response issues.
   * @return {Object[]} Processed issues.
   */
  RedmineNotifier.prototype.pickIssues = function(responseIssues) {
    var responseIssueCount = responseIssues.length;
    var lastExecutionTime = new Date(this._lastExecutionTime).getTime();
    var issues = [];
    var i;
    var updatedTime;

    for (i = 0; i < responseIssueCount; i++) {
      updatedTime = new Date(responseIssues[i].updated_on).getTime();

      if (updatedTime >= lastExecutionTime) {
        issues.push(responseIssues[i]);
      }
    }

    return issues;
  };

  /**
   * Test the connection to the Redmine.
   * @param {string} mode - Time or date.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.testConnection = function(mode) {
    var _this = this;
    var xhr = new XMLHttpRequest();
    var pageSettings = this.getPageSettings();

    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        _this.handleResponseTestConnection(mode, xhr.status);
      }
    };

    xhr.open('GET', pageSettings.url + '/issues.json' + this.getRequestParams(mode, pageSettings.projectId));
    xhr.setRequestHeader('X-Redmine-API-Key', pageSettings.apiKey);
    xhr.send();

    return this;
  };

  /**
   * Handle the response for the test connection.
   * @param {string} mode - Time or date.
   * @param {number} status - Response status.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.handleResponseTestConnection = function(mode, status) {
    if (status === 200) {
      notie.alert(1, 'Connection succeeded.', NOTIE_DISPLAY_SEC);
      return this;
    }

    // Retry with date mode if Redmine API doesn't accept time format
    if (mode === FETCH_MODE.TIME && status === 422) {
      this.testConnection(FETCH_MODE.DATE);
      return this;
    }

    notie.alert(3, 'Connection failed.', NOTIE_DISPLAY_SEC);

    return this;
  };

  /**
   * Get the request parameters.
   * @param {string} mode - Time or date.
   * @param {string} projectId - Project ID (a numeric value, not a project identifier).
   * @return {string} Request parameters.
   */
  RedmineNotifier.prototype.getRequestParams = function(mode, projectId) {
    var params = [
      'updated_on=%3E%3D' + this.getLastExecutionTime(mode),
      'sort=updated_on:desc'
    ];

    if (typeof projectId === 'string' && projectId !== '') {
      params.unshift('project_id=' + projectId);
    }

    return '?' + params.join('&');
  };

  /**
   * Get last execution time by mode.
   * @param {string} mode - Time or date.
   * @return {string} Last execution time.
   */
  RedmineNotifier.prototype.getLastExecutionTime = function(mode) {
    if (mode === FETCH_MODE.TIME) {
      return this._lastExecutionTime;
    } else {
      return this._lastExecutionTime.replace(/T.*/, ''); // Date
    }
  };

  /**
   * Send the desktop notification.
   * @param {Object} issues - All of updated issues.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.notify = function(issues) {
    var _this = this;
    var issueCount = issues.length;

    if (issueCount === 0) return this;

    var appDir = __dirname + '.unpacked'; // Production
    try {
      fs.statSync(appDir);
    } catch(e) {
      appDir = __dirname; // Development
    }

    _this._tray.setImage(_this._notificationIconFilePath);

    // Display the latest issue's subject only
    notifier.notify({
      title: '(' + issueCount + ') Redmine Notifier',
      message: issues[0].subject,
      icon: appDir + '/images/' + COLOR_ICON_FILENAME_64,
      wait: true
    });

    notifier.removeAllListeners();

    notifier.once('click', function() {
      _this._tray.setImage(_this._iconFilePath);

      shell.openExternal(_this._settings.url + '/issues/' + issues[0].id);
      notifier.removeAllListeners();
    });

    notifier.once('timeout', function() {
      notifier.removeAllListeners();
    });

    return this;
  };

  window.addEventListener('load', function() {
    var redmineNotifier = new RedmineNotifier();
    redmineNotifier.initMenu()
      .initEventListener()
      .displayDefaultSettings()
      .updateLastExecutionTime()
      .readStoredSettings()
      .displaySettings();

    if (redmineNotifier.validateSettings()) {
      redmineNotifier.initFetch();
    }
  });
}());

<commit_msg>Add "Open Most Recent Issue in Browser" to context menu<commit_after>'use strict';

(function() {
  const DEFAULT_FETCH_INTERVAL_SEC = 600;
  const NOTIE_DISPLAY_SEC = 1.5;
  const COLOR_ICON_FILENAME_64 = 'redmine_icon_color_64.png';
  const BLACK_ICON_FILENAME_24 = 'redmine_icon_black_24.png';
  const BLACK_ICON_FILENAME_24_NOTIFICATION = 'redmine_icon_black_24_notification.png';
  const COLOR_ICON_FILENAME_24 = 'redmine_icon_color_24.png';
  const COLOR_ICON_FILENAME_24_NOTIFICATION = 'redmine_icon_color_24_notification.png';

  var remote = window.require('remote');
  var shell = remote.require('shell');
  var Menu = remote.require('menu');
  var Tray = remote.require('tray');
  var fs = require('fs');
  var notie = require('notie');
  var notifier = require('node-notifier');

  var FETCH_MODE = Object.freeze({ TIME: 'TIME', DATE: 'DATE' });

  /**
   * Initialize the RedmineNotifier object.
   * @constructor
   */
  function RedmineNotifier() {
    this._tray = null;
    this._lastExecutionTime = null;
    this._settings = null;
    this._fetchTimer = null;
    this._fetchMode = null;
    this._contextMenu = null;
    this._mostRecentIssueId = null;

    if (process.platform === 'darwin') {
      this._iconFilePath             = __dirname + '/images/' + BLACK_ICON_FILENAME_24;
      this._notificationIconFilePath = __dirname + '/images/' + BLACK_ICON_FILENAME_24_NOTIFICATION;
    } else {
      this._iconFilePath             = __dirname + '/images/' + COLOR_ICON_FILENAME_24;
      this._notificationIconFilePath = __dirname + '/images/' + COLOR_ICON_FILENAME_24_NOTIFICATION;
    }
  }

  /**
   * Initialize the application menu and context menu.
   */
  RedmineNotifier.prototype.initMenu = function() {
    var _this = this;

    var appMenu = Menu.buildFromTemplate([
      {
        label: 'Edit',
        submenu: [
          { label: 'Undo',       accelerator: 'CmdOrCtrl+Z',       role: 'undo' },
          { label: 'Redo',       accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
          { 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' }
        ]
      }
    ]);

    this._contextMenu = Menu.buildFromTemplate([
      {
        label: 'Open Most Recent Issue in Browser',
        click: function() {
          shell.openExternal(_this._settings.url + '/issues/' + _this._mostRecentIssueId);
          _this.setNormalIcon();
        },
        enabled: false
      }, {
        label: 'Preferences',
        click: function() {
          remote.getCurrentWindow().show();
        }
      }, {
        label: 'Quit',
        click: function() {
          remote.app.quit();
        }
      }
    ]);

    Menu.setApplicationMenu(appMenu);

    _this._tray = new Tray(_this._iconFilePath);
    this._tray.setContextMenu(this._contextMenu);

    return this;
  };

  /**
   * Initialize the event listeners.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.initEventListener = function() {
    var _this = this;

    document.getElementById('save-button').addEventListener('click', function() {
      _this.readScreenSettings();

      if (_this.validateSettings()) {
        _this.initFetch()
          .updateSettings();
      } else {
        _this.readStoredSettings();
      }

      notie.alert(1, 'Settings have been saved.', NOTIE_DISPLAY_SEC);
    });

    document.getElementById('close-button').addEventListener('click', function() {
      _this.readStoredSettings()
        .displaySettings();
      remote.getCurrentWindow().hide();
    });

    document.getElementById('test-connection-button').addEventListener('click', function() {
      _this.testConnection(FETCH_MODE.TIME);
    });

    return this;
  };

  /**
   * Display the default settings on the screen.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.displayDefaultSettings = function() {
    document.getElementById('default-fetch-interval-sec').innerHTML = DEFAULT_FETCH_INTERVAL_SEC;
    return this;
  };

  /**
   * Display the settings on the screen.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.displaySettings = function() {
    document.getElementById('url').value = this._settings.url;
    document.getElementById('api-key').value = this._settings.apiKey;
    document.getElementById('project-id').value = this._settings.projectId;
    document.getElementById('fetch-interval-sec').value = this._settings.fetchIntervalSec;
    return this;
  };

  /**
   * Get the settings from the screen.
   * @return {Object} Settings.
   */
  RedmineNotifier.prototype.getPageSettings = function() {
    return {
      url: document.getElementById('url').value,
      apiKey: document.getElementById('api-key').value,
      projectId: document.getElementById('project-id').value,
      fetchIntervalSec: document.getElementById('fetch-interval-sec').value
    };
  };

  /**
   * Read the settings from the screen.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.readScreenSettings = function() {
    this._settings = this.getPageSettings();
    return this;
  };

  /**
   * Read the settings from the localStorage.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.readStoredSettings = function() {
    this._settings = {
      url: localStorage.getItem('url'),
      apiKey: localStorage.getItem('apiKey'),
      projectId: localStorage.getItem('projectId'),
      fetchIntervalSec: localStorage.getItem('fetchIntervalSec')
    };

    return this;
  };

  /**
   * Update the stored last execution time.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.updateLastExecutionTime = function() {
    this._lastExecutionTime = (new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
    localStorage.setItem('lastExecutionTime', this._lastExecutionTime);
    return this;
  };

  /**
   * Update the stored settings.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.updateSettings = function() {
    localStorage.setItem('url', this._settings.url);
    localStorage.setItem('apiKey', this._settings.apiKey);
    localStorage.setItem('projectId', this._settings.projectId);
    localStorage.setItem('fetchIntervalSec', this._settings.fetchIntervalSec);
    return this;
  };

  /**
   * Validate the settings.
   * @return {boolean} true if valid.
   */
  RedmineNotifier.prototype.validateSettings = function() {
    if (this._settings.url && this._settings.apiKey) {
      return true;
    } else {
      notie.alert(3, 'Please enter required fields.', NOTIE_DISPLAY_SEC);
      return false;
    }
  };

  /**
   * Initialize the fetch function.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.initFetch = function() {
    var _this = this;
    var intervalMsec = 1000 * (this._settings.fetchIntervalSec || DEFAULT_FETCH_INTERVAL_SEC);

    clearInterval(this._fetchTimer);

    this._fetchTimer = setInterval(function() {
      _this.fetch(_this._fetchMode || FETCH_MODE.TIME);
    }, intervalMsec);

    return this;
  };

  /**
   * Fetch updated issues by using Redmine REST API.
   * @param {string} mode - Time or date.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.fetch = function(mode) {
    var _this = this;
    var xhr = new XMLHttpRequest();

    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        _this.handleResponseFetch(mode, xhr.status, xhr.responseText);
      }
    };

    xhr.open('GET', this._settings.url + '/issues.json' + this.getRequestParams(mode, this._settings.projectId));
    xhr.setRequestHeader('X-Redmine-API-Key', this._settings.apiKey);
    xhr.send();

    this.setNormalIcon();

    return this;
  };

  /**
   * Handle the response for the fetch.
   * @param {string} mode - Time or date.
   * @param {number} status - Response status.
   * @param {string} responseText - Response text.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.handleResponseFetch = function(mode, status, responseText) {
    if (mode === FETCH_MODE.TIME) {
      if (status === 200) {
        this.notify(JSON.parse(responseText).issues)
          .updateLastExecutionTime();
      } else if (status === 422) {
        // Retry with date mode if Redmine API doesn't accept time format
        this._fetchMode = FETCH_MODE.DATE;
        this.fetch(FETCH_MODE.DATE);
      }
    } else {
      if (status === 200) {
        this.notify(this.pickIssues(JSON.parse(responseText).issues));
      }

      this.updateLastExecutionTime();
    }

    return this;
  };

  /**
   * Get issues which were updated after last execution time.
   * @param {string} responseIssues - Response issues.
   * @return {Object[]} Processed issues.
   */
  RedmineNotifier.prototype.pickIssues = function(responseIssues) {
    var responseIssueCount = responseIssues.length;
    var lastExecutionTime = new Date(this._lastExecutionTime).getTime();
    var issues = [];
    var i;
    var updatedTime;

    for (i = 0; i < responseIssueCount; i++) {
      updatedTime = new Date(responseIssues[i].updated_on).getTime();

      if (updatedTime >= lastExecutionTime) {
        issues.push(responseIssues[i]);
      }
    }

    return issues;
  };

  /**
   * Test the connection to the Redmine.
   * @param {string} mode - Time or date.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.testConnection = function(mode) {
    var _this = this;
    var xhr = new XMLHttpRequest();
    var pageSettings = this.getPageSettings();

    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        _this.handleResponseTestConnection(mode, xhr.status);
      }
    };

    xhr.open('GET', pageSettings.url + '/issues.json' + this.getRequestParams(mode, pageSettings.projectId));
    xhr.setRequestHeader('X-Redmine-API-Key', pageSettings.apiKey);
    xhr.send();

    return this;
  };

  /**
   * Handle the response for the test connection.
   * @param {string} mode - Time or date.
   * @param {number} status - Response status.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.handleResponseTestConnection = function(mode, status) {
    if (status === 200) {
      notie.alert(1, 'Connection succeeded.', NOTIE_DISPLAY_SEC);
      return this;
    }

    // Retry with date mode if Redmine API doesn't accept time format
    if (mode === FETCH_MODE.TIME && status === 422) {
      this.testConnection(FETCH_MODE.DATE);
      return this;
    }

    notie.alert(3, 'Connection failed.', NOTIE_DISPLAY_SEC);

    return this;
  };

  /**
   * Get the request parameters.
   * @param {string} mode - Time or date.
   * @param {string} projectId - Project ID (a numeric value, not a project identifier).
   * @return {string} Request parameters.
   */
  RedmineNotifier.prototype.getRequestParams = function(mode, projectId) {
    var params = [
      'updated_on=%3E%3D' + this.getLastExecutionTime(mode),
      'sort=updated_on:desc'
    ];

    if (typeof projectId === 'string' && projectId !== '') {
      params.unshift('project_id=' + projectId);
    }

    return '?' + params.join('&');
  };

  /**
   * Get last execution time by mode.
   * @param {string} mode - Time or date.
   * @return {string} Last execution time.
   */
  RedmineNotifier.prototype.getLastExecutionTime = function(mode) {
    if (mode === FETCH_MODE.TIME) {
      return this._lastExecutionTime;
    } else {
      return this._lastExecutionTime.replace(/T.*/, ''); // Date
    }
  };

  /**
   * Send the desktop notification.
   * @param {Object} issues - All of updated issues.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.notify = function(issues) {
    var _this = this;
    var issueCount = issues.length;

    if (issueCount === 0) return this;

    var appDir = __dirname + '.unpacked'; // Production
    try {
      fs.statSync(appDir);
    } catch(e) {
      appDir = __dirname; // Development
    }

    this.setNotificationIcon(issues[0].id);

    // Display the latest issue's subject only
    notifier.notify({
      title: '(' + issueCount + ') Redmine Notifier',
      message: issues[0].subject,
      icon: appDir + '/images/' + COLOR_ICON_FILENAME_64,
      wait: true
    });

    notifier.removeAllListeners();

    notifier.once('click', function() {
      shell.openExternal(_this._settings.url + '/issues/' + _this._mostRecentIssueId);
      _this.setNormalIcon();
      notifier.removeAllListeners();
    });

    notifier.once('timeout', function() {
      notifier.removeAllListeners();
    });

    return this;
  };

  /**
   * Set normal icon and disable "Open Most Recent Issue in Browser" in context menu.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.setNormalIcon = function() {
    this._tray.setImage(this._iconFilePath);
    this._contextMenu.items[0].enabled = false;
    this._mostRecentIssueId = null;

    return this;
  };

  /**
   * Set notification icon and enable "Open Most Recent Issue in Browser" in context menu.
   * @param {number} issueId - Most recent issue ID.
   * @return {Object} Current object.
   */
  RedmineNotifier.prototype.setNotificationIcon = function(issueId) {
    this._tray.setImage(this._notificationIconFilePath);
    this._contextMenu.items[0].enabled = true;
    this._mostRecentIssueId = issueId;

    return this;
  };

  window.addEventListener('load', function() {
    var redmineNotifier = new RedmineNotifier();
    redmineNotifier.initMenu()
      .initEventListener()
      .displayDefaultSettings()
      .updateLastExecutionTime()
      .readStoredSettings()
      .displaySettings();

    if (redmineNotifier.validateSettings()) {
      redmineNotifier.initFetch();
    }
  });
}());

<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var chalk = require('chalk');

var EdgeplateGenerator = yeoman.generators.Base.extend({
    init: function init() {
        this.pkg = require('../package.json');
        this.edgeplate = this.config.getAll();
        this.currentYear = new Date().getFullYear();
        if (this.edgeplate.created) {
            this.edgeplate.modified = Date.now();
        } else {
            this.edgeplate.version = this.pkg.version;
            this.edgeplate.created = Date.now();
            this.edgeplate.pages = {
                Index: {
                    state: this._.slugify('Index')
                }
            };
        }

        this.on('end', function () {
            this.config.defaults(this.edgeplate);
            if (!this.options['skip-install']) {
                this.installDependencies({
                    callback: function () {
                        var sourceRoot = process.cwd();
                        var fontSrc = path.join(sourceRoot, '/app/bower_lib/bootstrap-sass/assets/fonts/bootstrap');
                        var fontDest = path.join(sourceRoot, 'app/styles/fonts');
                        var file = require('yeoman-generator').file;

                        file.recurse(fontSrc, function callback(abspath, rootdir, subdir, filename) {
                            file.copy(fontSrc + '/' + filename, fontDest + '/' + filename);
                        });

                        var motionSrc = path.join(sourceRoot, '/app/bower_lib/angular-motion/dist/angular-motion.css');

                        file.copy(motionSrc, path.join(sourceRoot, 'app/styles/_angular-motion.scss'));

                        if (this.edgeplate.features.cordova) {
                            this.log
                                .write()
                                .write()
                                .info(chalk.yellow('Action required to complete Cordova support'))
                                .write()
                                .info('Enter the cordova directory')
                                .info(chalk.grey('$'), chalk.cyan('cd cordova'))
                                .write()
                                .info('Add platforms to support')
                                .info(chalk.grey('$'), chalk.cyan('cordova platform add ios'))
                                .write()
                                .info('Add required plugins')
                                .info(chalk.grey('$'), chalk.cyan('cordova plugin add org.apache.cordova.console'))
                                .info(chalk.grey('$'), chalk.cyan('cordova plugin add org.apache.cordova.device'))
                                .info(chalk.grey('$'), chalk.cyan('cordova plugin add org.apache.cordova.statusbar'))
                                .write();
                        }
                    }.bind(this)
                });
            }
        });
    },
    isProject: function isProject() {
        // TODO: Move to a library item so it is accessible for sub generators
        if (this.edgeplate.version) {
            this.isEdgeplate = true;
            var thisVersion = this.pkg.version.split('.');
            var thatVersion = this.edgeplate.version.split('.');
            if (thisVersion[0] !== thatVersion[0] || thisVersion[1] !== thatVersion[1]) {
                // only look at major and minor version
                this.hasConflict = true;
            } else {
                this.hasConflict = false;
            }
        } else {
            this.isEdgeplate = false;
        }

        if (this.hasConflict) {
            this.log
                .write()
                .info(chalk.yellow('WARNING - Generator Mismatch'))
                .info('You are currently using Edgeplate ' + chalk.yellow('v' + this.pkg.version) + '.')
                .info('The original Edgeplate generator used for this project was ' + chalk.yellow('v' + this.edgeplate.version) + '.')
                .write();

            var done = this.async();

            var prompts = [{
                type: 'confirm',
                name: 'continue',
                message: 'Do you wish to continue?',
                default: false
            }];

            this.prompt(prompts, function (props) {
                if (!props.continue) {
                    process.exit(1);
                }
                done();
            }.bind(this));
        }
    },
    welcome: function welcome() {
        var done = this.async();

        var welcome = [
            chalk.gray('   _____ ____   ____ _____ ____  _        _  _____ _____   '),
            chalk.gray('  | ____|  _ \\ / ___| ____|  _ \\| |      / \\|_   _| ____|  '),
            chalk.gray('  |  _| | | | | |  _|  _| | |_) | |     / _ \\ | | |  _|    '),
            chalk.gray('  | |___| |_| | |_| | |___|  __/| |___ / ___ \\| | | |___   '),
            chalk.gray('  |_____|____/ \\____|_____|_|   |_____/_/   \\_\\_| |_____|  '),
            chalk.gray('                                                           '),
            '                     ' + chalk.green('Edgeplate v' + this.pkg.version) + '                    ',
            '               ' + chalk.green('by Ron Edgecomb and Joe Kovach') + '            ',
            '',
            '      Edgeplate is an opinionated way of building web',
            '       applications with Angular, Loopback and Node.',
            ''
        ].join('\n');
        this.log(welcome);

        var prompts = [{
            name: 'title',
            message: 'What is the display title of your app?',
            default: 'Edge Project'
        }];

        this.prompt(prompts, function (props) {
            this.edgeplate.title = props.title;
            done();
        }.bind(this));
    },
    projectSlug: function projectSlug() {
        var done = this.async();
        var prompts = [{
            name: 'slug',
            message: 'Enter slug to use with project',
            default: this._.slugify(this.edgeplate.title),
            validate: function (slug) {
                if (!/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/.test(slug)) {
                    return 'Please use a valid slug format';
                } else {
                    return true;
                }
            }
        }];
        this.prompt(prompts, function (props) {
            this.edgeplate.slug = props.slug;
            done();
        }.bind(this));
    },
    features: function features() {
        var done = this.async();
        var prompts = [{
            type: 'checkbox',
            name: 'features',
            message: 'What features would you like supported?',
            choices: [{
                name: 'Cordova',
                value: 'cordova'
            },
            {
                name: 'Loopback',
                value: 'loopback'
            },
            {
                name: 'Socket.io',
                value: 'socketio'
            },
            {
                name: 'Build and Deploy',
                value: 'buildDeploy'
            },
            {
                name: 'Favicon Builder',
                value: 'favicon'
            }],
            default: ['cordova', 'loopback', 'socketio', 'buildDeploy', 'favicon']
        }];
        this.prompt(prompts, function (props) {
            this.edgeplate.features = {};
            props.features.forEach(function (feature) {
                this.edgeplate.features[feature] = true;
            }.bind(this));
            done();
        }.bind(this));
    },
    buildAndDeploy: function buildAndDeploy() {
        if (!this.edgeplate.features.buildDeploy) {
            return;
        }

        this.log
            .write()
            .write()
            .info(chalk.green('Please complete Edgeplate options for build and deploy scripts.'))
            .write();

        var done = this.async();

        var prompts = [{
            name: 'hostDomain',
            message: 'Where is your project hosted (server name)?',
            default: 'host.edgeplate.com'
        },
        {
            name: 'baseDomain',
            message: 'What is your base domain for this project?',
            default: 'project.edgeplate.com'
        },
        {
            name: 'wwwPort',
            message: 'What port will your express production service run on?',
            default: '80'
        },
        {
            name: 'devPort',
            message: 'What port will your express development service run on?',
            default: '8000'
        },
        {
            name: 'sshPort',
            message: 'What port will you use for ssh?',
            default: '22'
        }];

        this.prompt(prompts, function (props) {
            this.edgeplate.hostDomain = props.hostDomain;
            this.edgeplate.baseDomain = props.baseDomain;
            this.edgeplate.devPort = props.devPort;
            this.edgeplate.wwwPort = props.wwwPort;
            this.edgeplate.sshPort = props.sshPort;
            done();
        }.bind(this));
    },
    buildDeployFiles: function buildDeployFiles() {
        if (this.edgeplate.features.buildDeploy) {
            this.src.copy('excludes', '.excludes');
            this.src.copy('deploy.sh', 'deploy.sh');
            this.src.copy('remote.sh', 'remote.sh');
            this.src.copy('tail-log.sh', 'tail-log.sh');
        }
    },
    faviconFiles: function faviconFiles() {
        if (this.edgeplate.features.favicon) {
            this.src.copy('favicon.sh', 'favicon.sh');
        }
    },
    rootFiles: function rootFiles() {
        this.template('_gulpfile.js', 'gulpfile.js');
        this.template('README.md', 'README.md');
        this.template('_bower.json', 'bower.json');
        this.template('_package.json', 'package.json');
        this.src.copy('bowerrc', '.bowerrc');
        this.src.copy('editorconfig', '.editorconfig');
        this.src.copy('jshintrc', '.jshintrc');
        this.src.copy('gitignore', '.gitignore');
    },
    layoutFiles: function layoutFiles() {
        this.template('app/index.jade', 'app/index.jade');
        this.template('app/layout/_global.jade', 'app/layout/_global.jade');
        this.src.copy('app/layout/_touchIcons.jade', 'app/layout/_touchIcons.jade');
        this.src.copy('app/layout/_browserWarnings.jade', 'app/layout/_browserWarnings.jade');
    },
    componentFiles: function componentFiles() {
        this.src.copy('app/components/navbar/_navbar.scss', 'app/components/navbar/_navbar.scss');
        this.template('app/components/navbar/navbar.jade', 'app/components/navbar/navbar.jade');
    },
    directiveFiles: function directiveFiles() {
        this.dest.mkdir('app/directives');
    },
    imageFiles: function imageFiles() {
        this.directory('app/images/favicon/', 'app/images/favicon/');
        this.src.copy('app/favicon.ico', 'app/favicon.ico');
    },
    filterFiles: function filterFiles() {
        this.dest.mkdir('app/scripts/filters');
    },
    serviceFiles: function serviceFiles() {
        this.src.copy('app/scripts/services/edgePage.js', 'app/scripts/services/edgePage.js');
        this.src.copy('app/scripts/services/edgeResolver.js', 'app/scripts/services/edgeResolver.js');
        this.src.copy('app/scripts/services/NProgress.js', 'app/scripts/services/NProgress.js');
    },
    scriptFiles: function scriptFiles() {
        this.template('app/scripts/app.js', 'app/scripts/app.js');
        this.src.copy('app/scripts/helper.js', 'app/scripts/helper.js');
        this.src.copy('app/scripts/foot-init.js', 'app/scripts/foot-init.js');
        this.src.copy('app/scripts/head-init.js', 'app/scripts/head-init.js');
    },
    styleFiles: function styleFiles() {
        this.dest.mkdir('app/styles');
        this.src.copy('app/styles/_animate.scss', 'app/styles/_animate.scss');
        this.src.copy('app/styles/_mixins.scss', 'app/styles/_mixins.scss');
        this.src.copy('app/styles/_nprogress.scss', 'app/styles/_nprogress.scss');
        this.src.copy('app/styles/_styles.scss', 'app/styles/_styles.scss');
        this.src.copy('app/styles/_variables.scss', 'app/styles/_variables.scss');
        this.src.copy('app/styles/app.scss', 'app/styles/app.scss');
    },
    controllerFiles: function controllerFiles() {
        this.template('app/controllers/App/AppController.js', 'app/controllers/App/AppController.js');
        this.src.copy('app/controllers/pages/Index/_Index.scss', 'app/controllers/pages/Index/_Index.scss');
        this.src.copy('app/controllers/pages/Index/IndexController.js', 'app/controllers/pages/Index/IndexController.js');
        this.src.copy('app/controllers/pages/Index/IndexResolver.js', 'app/controllers/pages/Index/IndexResolver.js');
        this.template('app/controllers/pages/Index/IndexView.jade', 'app/controllers/pages/Index/IndexView.jade');
    },
    cordovaFiles: function cordovaFiles() {
        if (this.edgeplate.features.cordova) {
            this.template('cordova/hooks/README.md', 'cordova/hooks/README.md');
            this.dest.mkdir('cordova/merges');
            this.dest.mkdir('cordova/platforms');
            this.dest.mkdir('cordova/plugins');
            this.dest.mkdir('cordova/www');
            this.template('cordova/config.xml', 'cordova/config.xml');
            this.src.copy('app/scripts/edge.cordova.js', 'app/scripts/edge.cordova.js');
        }
    },
    loopbackFiles: function loopbackFiles() {
        if (this.edgeplate.features.loopback) {
            this.src.copy('app/scripts/loopback.js', 'app/scripts/loopback.js');
            this.directory('server/', 'server/');
        }
    },
    commonFiles: function commonFiles() {
        this.directory('common/', 'common/');
    },
    socketioFiles: function commonFiles() {
        if (this.edgeplate.features.socketio) {
            this.src.copy('app/scripts/services/socketio.js', 'app/scripts/services/socketio.js');
        }
    }
});

module.exports = EdgeplateGenerator;
<commit_msg>copy scss-lint.yml<commit_after>'use strict';
var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var chalk = require('chalk');

var EdgeplateGenerator = yeoman.generators.Base.extend({
    init: function init() {
        this.pkg = require('../package.json');
        this.edgeplate = this.config.getAll();
        this.currentYear = new Date().getFullYear();
        if (this.edgeplate.created) {
            this.edgeplate.modified = Date.now();
        } else {
            this.edgeplate.version = this.pkg.version;
            this.edgeplate.created = Date.now();
            this.edgeplate.pages = {
                Index: {
                    state: this._.slugify('Index')
                }
            };
        }

        this.on('end', function () {
            this.config.defaults(this.edgeplate);
            if (!this.options['skip-install']) {
                this.installDependencies({
                    callback: function () {
                        var sourceRoot = process.cwd();
                        var fontSrc = path.join(sourceRoot, '/app/bower_lib/bootstrap-sass/assets/fonts/bootstrap');
                        var fontDest = path.join(sourceRoot, 'app/styles/fonts');
                        var file = require('yeoman-generator').file;

                        file.recurse(fontSrc, function callback(abspath, rootdir, subdir, filename) {
                            file.copy(fontSrc + '/' + filename, fontDest + '/' + filename);
                        });

                        var motionSrc = path.join(sourceRoot, '/app/bower_lib/angular-motion/dist/angular-motion.css');

                        file.copy(motionSrc, path.join(sourceRoot, 'app/styles/_angular-motion.scss'));

                        if (this.edgeplate.features.cordova) {
                            this.log
                                .write()
                                .write()
                                .info(chalk.yellow('Action required to complete Cordova support'))
                                .write()
                                .info('Enter the cordova directory')
                                .info(chalk.grey('$'), chalk.cyan('cd cordova'))
                                .write()
                                .info('Add platforms to support')
                                .info(chalk.grey('$'), chalk.cyan('cordova platform add ios'))
                                .write()
                                .info('Add required plugins')
                                .info(chalk.grey('$'), chalk.cyan('cordova plugin add org.apache.cordova.console'))
                                .info(chalk.grey('$'), chalk.cyan('cordova plugin add org.apache.cordova.device'))
                                .info(chalk.grey('$'), chalk.cyan('cordova plugin add org.apache.cordova.statusbar'))
                                .write();
                        }
                    }.bind(this)
                });
            }
        });
    },
    isProject: function isProject() {
        // TODO: Move to a library item so it is accessible for sub generators
        if (this.edgeplate.version) {
            this.isEdgeplate = true;
            var thisVersion = this.pkg.version.split('.');
            var thatVersion = this.edgeplate.version.split('.');
            if (thisVersion[0] !== thatVersion[0] || thisVersion[1] !== thatVersion[1]) {
                // only look at major and minor version
                this.hasConflict = true;
            } else {
                this.hasConflict = false;
            }
        } else {
            this.isEdgeplate = false;
        }

        if (this.hasConflict) {
            this.log
                .write()
                .info(chalk.yellow('WARNING - Generator Mismatch'))
                .info('You are currently using Edgeplate ' + chalk.yellow('v' + this.pkg.version) + '.')
                .info('The original Edgeplate generator used for this project was ' + chalk.yellow('v' + this.edgeplate.version) + '.')
                .write();

            var done = this.async();

            var prompts = [{
                type: 'confirm',
                name: 'continue',
                message: 'Do you wish to continue?',
                default: false
            }];

            this.prompt(prompts, function (props) {
                if (!props.continue) {
                    process.exit(1);
                }
                done();
            }.bind(this));
        }
    },
    welcome: function welcome() {
        var done = this.async();

        var welcome = [
            chalk.gray('   _____ ____   ____ _____ ____  _        _  _____ _____   '),
            chalk.gray('  | ____|  _ \\ / ___| ____|  _ \\| |      / \\|_   _| ____|  '),
            chalk.gray('  |  _| | | | | |  _|  _| | |_) | |     / _ \\ | | |  _|    '),
            chalk.gray('  | |___| |_| | |_| | |___|  __/| |___ / ___ \\| | | |___   '),
            chalk.gray('  |_____|____/ \\____|_____|_|   |_____/_/   \\_\\_| |_____|  '),
            chalk.gray('                                                           '),
            '                     ' + chalk.green('Edgeplate v' + this.pkg.version) + '                    ',
            '               ' + chalk.green('by Ron Edgecomb and Joe Kovach') + '            ',
            '',
            '      Edgeplate is an opinionated way of building web',
            '       applications with Angular, Loopback and Node.',
            ''
        ].join('\n');
        this.log(welcome);

        var prompts = [{
            name: 'title',
            message: 'What is the display title of your app?',
            default: 'Edge Project'
        }];

        this.prompt(prompts, function (props) {
            this.edgeplate.title = props.title;
            done();
        }.bind(this));
    },
    projectSlug: function projectSlug() {
        var done = this.async();
        var prompts = [{
            name: 'slug',
            message: 'Enter slug to use with project',
            default: this._.slugify(this.edgeplate.title),
            validate: function (slug) {
                if (!/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/.test(slug)) {
                    return 'Please use a valid slug format';
                } else {
                    return true;
                }
            }
        }];
        this.prompt(prompts, function (props) {
            this.edgeplate.slug = props.slug;
            done();
        }.bind(this));
    },
    features: function features() {
        var done = this.async();
        var prompts = [{
            type: 'checkbox',
            name: 'features',
            message: 'What features would you like supported?',
            choices: [{
                name: 'Cordova',
                value: 'cordova'
            },
            {
                name: 'Loopback',
                value: 'loopback'
            },
            {
                name: 'Socket.io',
                value: 'socketio'
            },
            {
                name: 'Build and Deploy',
                value: 'buildDeploy'
            },
            {
                name: 'Favicon Builder',
                value: 'favicon'
            }],
            default: ['cordova', 'loopback', 'socketio', 'buildDeploy', 'favicon']
        }];
        this.prompt(prompts, function (props) {
            this.edgeplate.features = {};
            props.features.forEach(function (feature) {
                this.edgeplate.features[feature] = true;
            }.bind(this));
            done();
        }.bind(this));
    },
    buildAndDeploy: function buildAndDeploy() {
        if (!this.edgeplate.features.buildDeploy) {
            return;
        }

        this.log
            .write()
            .write()
            .info(chalk.green('Please complete Edgeplate options for build and deploy scripts.'))
            .write();

        var done = this.async();

        var prompts = [{
            name: 'hostDomain',
            message: 'Where is your project hosted (server name)?',
            default: 'host.edgeplate.com'
        },
        {
            name: 'baseDomain',
            message: 'What is your base domain for this project?',
            default: 'project.edgeplate.com'
        },
        {
            name: 'wwwPort',
            message: 'What port will your express production service run on?',
            default: '80'
        },
        {
            name: 'devPort',
            message: 'What port will your express development service run on?',
            default: '8000'
        },
        {
            name: 'sshPort',
            message: 'What port will you use for ssh?',
            default: '22'
        }];

        this.prompt(prompts, function (props) {
            this.edgeplate.hostDomain = props.hostDomain;
            this.edgeplate.baseDomain = props.baseDomain;
            this.edgeplate.devPort = props.devPort;
            this.edgeplate.wwwPort = props.wwwPort;
            this.edgeplate.sshPort = props.sshPort;
            done();
        }.bind(this));
    },
    buildDeployFiles: function buildDeployFiles() {
        if (this.edgeplate.features.buildDeploy) {
            this.src.copy('excludes', '.excludes');
            this.src.copy('deploy.sh', 'deploy.sh');
            this.src.copy('remote.sh', 'remote.sh');
            this.src.copy('tail-log.sh', 'tail-log.sh');
        }
    },
    faviconFiles: function faviconFiles() {
        if (this.edgeplate.features.favicon) {
            this.src.copy('favicon.sh', 'favicon.sh');
        }
    },
    rootFiles: function rootFiles() {
        this.template('_gulpfile.js', 'gulpfile.js');
        this.template('README.md', 'README.md');
        this.template('_bower.json', 'bower.json');
        this.template('_package.json', 'package.json');
        this.src.copy('bowerrc', '.bowerrc');
        this.src.copy('editorconfig', '.editorconfig');
        this.src.copy('jshintrc', '.jshintrc');
        this.src.copy('gitignore', '.gitignore');
        this.src.copy('scss-lint.yml', '.scss-lint.yml');
    },
    layoutFiles: function layoutFiles() {
        this.template('app/index.jade', 'app/index.jade');
        this.template('app/layout/_global.jade', 'app/layout/_global.jade');
        this.src.copy('app/layout/_touchIcons.jade', 'app/layout/_touchIcons.jade');
        this.src.copy('app/layout/_browserWarnings.jade', 'app/layout/_browserWarnings.jade');
    },
    componentFiles: function componentFiles() {
        this.src.copy('app/components/navbar/_navbar.scss', 'app/components/navbar/_navbar.scss');
        this.template('app/components/navbar/navbar.jade', 'app/components/navbar/navbar.jade');
    },
    directiveFiles: function directiveFiles() {
        this.dest.mkdir('app/directives');
    },
    imageFiles: function imageFiles() {
        this.directory('app/images/favicon/', 'app/images/favicon/');
        this.src.copy('app/favicon.ico', 'app/favicon.ico');
    },
    filterFiles: function filterFiles() {
        this.dest.mkdir('app/scripts/filters');
    },
    serviceFiles: function serviceFiles() {
        this.src.copy('app/scripts/services/edgePage.js', 'app/scripts/services/edgePage.js');
        this.src.copy('app/scripts/services/edgeResolver.js', 'app/scripts/services/edgeResolver.js');
        this.src.copy('app/scripts/services/NProgress.js', 'app/scripts/services/NProgress.js');
    },
    scriptFiles: function scriptFiles() {
        this.template('app/scripts/app.js', 'app/scripts/app.js');
        this.src.copy('app/scripts/helper.js', 'app/scripts/helper.js');
        this.src.copy('app/scripts/foot-init.js', 'app/scripts/foot-init.js');
        this.src.copy('app/scripts/head-init.js', 'app/scripts/head-init.js');
    },
    styleFiles: function styleFiles() {
        this.dest.mkdir('app/styles');
        this.src.copy('app/styles/_animate.scss', 'app/styles/_animate.scss');
        this.src.copy('app/styles/_mixins.scss', 'app/styles/_mixins.scss');
        this.src.copy('app/styles/_nprogress.scss', 'app/styles/_nprogress.scss');
        this.src.copy('app/styles/_styles.scss', 'app/styles/_styles.scss');
        this.src.copy('app/styles/_variables.scss', 'app/styles/_variables.scss');
        this.src.copy('app/styles/app.scss', 'app/styles/app.scss');
    },
    controllerFiles: function controllerFiles() {
        this.template('app/controllers/App/AppController.js', 'app/controllers/App/AppController.js');
        this.src.copy('app/controllers/pages/Index/_Index.scss', 'app/controllers/pages/Index/_Index.scss');
        this.src.copy('app/controllers/pages/Index/IndexController.js', 'app/controllers/pages/Index/IndexController.js');
        this.src.copy('app/controllers/pages/Index/IndexResolver.js', 'app/controllers/pages/Index/IndexResolver.js');
        this.template('app/controllers/pages/Index/IndexView.jade', 'app/controllers/pages/Index/IndexView.jade');
    },
    cordovaFiles: function cordovaFiles() {
        if (this.edgeplate.features.cordova) {
            this.template('cordova/hooks/README.md', 'cordova/hooks/README.md');
            this.dest.mkdir('cordova/merges');
            this.dest.mkdir('cordova/platforms');
            this.dest.mkdir('cordova/plugins');
            this.dest.mkdir('cordova/www');
            this.template('cordova/config.xml', 'cordova/config.xml');
            this.src.copy('app/scripts/edge.cordova.js', 'app/scripts/edge.cordova.js');
        }
    },
    loopbackFiles: function loopbackFiles() {
        if (this.edgeplate.features.loopback) {
            this.src.copy('app/scripts/loopback.js', 'app/scripts/loopback.js');
            this.directory('server/', 'server/');
        }
    },
    commonFiles: function commonFiles() {
        this.directory('common/', 'common/');
    },
    socketioFiles: function commonFiles() {
        if (this.edgeplate.features.socketio) {
            this.src.copy('app/scripts/services/socketio.js', 'app/scripts/services/socketio.js');
        }
    }
});

module.exports = EdgeplateGenerator;
<|endoftext|>


<commit_before>'use strict';

var generators = require('yeoman-generator');
var path = require('path');
var crypto = require('crypto');

module.exports = generators.Base.extend({
  initializing: function () {
    this.pkg = this.fs.readJSON(this.destinationPath('package.json'), {});
    this.props = {
      name: process.cwd().split(path.sep).pop()
    };
  },

  prompting: function () {
    var done = this.async();
    var prompts = [
      {
        name: 'name',
        message: 'Project name',
        when: !this.pkg.name,
        default: this.props.name
      },
      {
        name: 'description',
        message: 'Description',
        when: !this.pkg.description
      },
      {
        type: 'checkbox',
        name: 'providers',
        message: 'What type of API are you making?',
        choices: [
          {
            name: 'REST',
            value: 'rest',
            checked: true
          },
          {
            name: 'Realtime via Socket.io',
            value: 'socket.io',
            checked: true
          },
          {
            name: 'Realtime via Primus',
            value: 'primus',
          }
        ]
      },
      {
        type: 'list',
        name: 'cors',
        message: 'CORS configuration',
        choices: [
          {
            name: 'Enabled for all domains',
            value: 'enabled',
            checked: true
          },
          {
            name: 'Enabled for whitelisted domains',
            value: 'whitelisted'
          },
          {
            name: 'Disabled',
            value: false
          }
        ]
      },
      {
        type: 'input',
        name: 'corsWhitelist',
        message: 'Comma-separated domains for CORS whitelist. Include http(s)',
        when: function(props){
          return props.cors === 'whitelisted';
        }
      },
      {
        type: 'list',
        name: 'database',
        message: 'What database do you primarily want to use?',
        choices: [
          {
            name: 'I will choose my own',
            checked: true
          },
          {
            name: 'Memory',
            value: 'memory'
          },
          {
            name: 'MongoDB',
            value: 'mongodb'
          },
          {
            name: 'MySQL',
            value: 'mysql'
          },
          {
            name: 'MariaDB',
            value: 'mariadb'
          },
          {
            name: 'NeDB',
            value: 'nedb'
          },
          {
            name: 'PostgreSQL',
            value: 'postgres'
          },
          {
            name: 'SQLite',
            value: 'sqlite'
          },
          {
           name: 'SQL Server',
           value: 'mssql'
          }
        ]
      },
      {
        type: 'checkbox',
        name: 'authentication',
        message: 'What authentication methods would you like to support?',
        choices: [
          {
            name: 'local',
            checked: true
          }
        //   name: 'basic'
        // }, {

        // }, {
        //   name: 'google'
        // }, {
        //   name: 'facebook'
        // }, {
        //   name: 'twitter'
        // }, {
        //   name: 'github'
        ]
      }
    ];

    this.prompt(prompts, function (props) {
      this.props = Object.assign(this.props, props);

      done();
    }.bind(this));
  },

  writing: function () {
    this.props.secret = crypto.randomBytes(64).toString('base64');
    this.props.corsWhitelist = this.props.corsWhitelist && this.props.corsWhitelist.split(',');
    var dependencies = [
      'feathers@2.0.0-pre.2',
      'feathers-hooks@1.0.0-pre.2',
      'feathers-errors',
      'feathers-configuration',
      'serve-favicon',
      'compression',
      'winston',
      'babel-core',
      'babel-preset-es2015'
    ];

    if (this.props.providers.indexOf('rest') !== -1) {
      dependencies.push('body-parser');
      dependencies.push('feathers-rest');
    }

    if (this.props.providers.indexOf('socket.io') !== -1) {
      dependencies.push('feathers-socketio');
    }

    if (this.props.providers.indexOf('primus') !== -1) {
      dependencies.push('feathers-primus');
      dependencies.push('sockjs');
    }

    if (this.props.authentication.length) {
      dependencies.push('feathers-authentication');
    }

    if (this.props.cors) {
      dependencies.push('cors');
    }

    switch(this.props.database) {
      case 'memory':
        dependencies.push('feathers-memory');
        this.fs.copyTpl(
          this.templatePath('services/memory-user.js'),
          this.destinationPath('server/services', 'user.js'),
          this.props
        );
        break;
      case 'mongodb':
        dependencies.push('mongoose');
        dependencies.push('feathers-mongoose');
        this.fs.copyTpl(
          this.templatePath('models/mongoose-user.js'),
          this.destinationPath('server/models', 'user.js'),
          this.props
        );
        this.fs.copyTpl(
          this.templatePath('services/mongoose-user.js'),
          this.destinationPath('server/services', 'user.js'),
          this.props
        );
        break;
      case 'mysql':
      case 'mariadb':
        dependencies.push('mysql');
        dependencies.push('sequelize');
        dependencies.push('feathers-sequelize');
        this.fs.copyTpl(
          this.templatePath('models/sequelize-user.js'),
          this.destinationPath('server/models', 'user.js'),
          this.props
        );
        this.fs.copyTpl(
          this.templatePath('services/sequelize-user.js'),
          this.destinationPath('server/services', 'user.js'),
          this.props
        );
        break;
      case 'nedb':
        dependencies.push('nedb');
        dependencies.push('feathers-nedb');
        this.fs.copyTpl(
          this.templatePath('services/nedb-user.js'),
          this.destinationPath('server/services', 'user.js'),
          this.props
        );
        break;
      case 'postgres':
        dependencies.push('pg');
        dependencies.push('pg-hstore');
        dependencies.push('sequelize');
        dependencies.push('feathers-sequelize');
        this.fs.copyTpl(
          this.templatePath('models/sequelize-user.js'),
          this.destinationPath('server/models', 'user.js'),
          this.props
        );
        this.fs.copyTpl(
          this.templatePath('services/sequelize-user.js'),
          this.destinationPath('server/services', 'user.js'),
          this.props
        );
        break;
      case 'sqlite':
        dependencies.push('sqlite3');
        dependencies.push('sequelize');
        dependencies.push('feathers-sequelize');
        this.fs.copyTpl(
          this.templatePath('models/sequelize-user.js'),
          this.destinationPath('server/models', 'user.js'),
          this.props
        );
        this.fs.copyTpl(
          this.templatePath('services/sequelize-user.js'),
          this.destinationPath('server/services', 'user.js'),
          this.props
        );
        break;
      case 'mssql':
        dependencies.push('tedious');
        dependencies.push('sequelize');
        dependencies.push('feathers-sequelize');
        this.fs.copyTpl(
          this.templatePath('models/sequelize-user.js'),
          this.destinationPath('server/models', 'user.js'),
          this.props
        );
        this.fs.copyTpl(
          this.templatePath('services/sequelize-user.js'),
          this.destinationPath('server/services', 'user.js'),
          this.props
        );
        break;
    }

    this.fs.copy(this.templatePath('static'), this.destinationPath());
    this.fs.copy(this.templatePath('static/.*'), this.destinationPath());

    this.fs.copyTpl(
      this.templatePath('app.js'),
      this.destinationPath('server', 'app.js'),
      this.props
    );

    this.fs.copyTpl(
      this.templatePath('middleware.js'),
      this.destinationPath('server/middleware', 'index.js'),
      this.props
    );

    this.fs.copyTpl(
      this.templatePath('service.js'),
      this.destinationPath('server/services', 'index.js'),
      this.props
    );

    this.fs.copyTpl(
      this.templatePath('config.default.json'),
      this.destinationPath('config', 'default.json'),
      this.props
    );

    this.fs.copyTpl(
      this.templatePath('config.production.json'),
      this.destinationPath('config', 'production.json'),
      this.props
    );

    this.fs.copyTpl(
      this.templatePath('package.json'),
      this.destinationPath('package.json'),
      this.props
    );

    this.log(this.props);

    this.npmInstall(dependencies, { save: true });

    this.npmInstall([
      'jshint',
      'mocha',
      'request'
    ], { saveDev: true});
  }
});
<commit_msg>cleaning up the main app generator and using generator composition<commit_after>'use strict';

var generators = require('yeoman-generator');
var path = require('path');
var crypto = require('crypto');

module.exports = generators.Base.extend({
  initializing: function () {
    this.pkg = this.fs.readJSON(this.destinationPath('package.json'), {});
    this.props = {
      name: process.cwd().split(path.sep).pop()
    };
    this.dependencies = [
      'feathers@2.0.0-pre.2',
      'feathers-hooks@1.0.0-pre.2',
      'feathers-errors',
      'feathers-configuration',
      'serve-favicon',
      'compression',
      'winston',
      'babel-core',
      'babel-preset-es2015'
    ];
  },

  prompting: function () {
    var done = this.async();
    var prompts = [
      {
        name: 'name',
        message: 'Project name',
        when: !this.pkg.name,
        default: this.props.name
      },
      {
        name: 'description',
        message: 'Description',
        when: !this.pkg.description
      },
      {
        type: 'checkbox',
        name: 'providers',
        message: 'What type of API are you making?',
        choices: [
          {
            name: 'REST',
            value: 'rest',
            checked: true
          },
          {
            name: 'Realtime via Socket.io',
            value: 'socket.io',
            checked: true
          },
          {
            name: 'Realtime via Primus',
            value: 'primus',
          }
        ]
      },
      {
        type: 'list',
        name: 'cors',
        message: 'CORS configuration',
        choices: [
          {
            name: 'Enabled for all domains',
            value: 'enabled',
            checked: true
          },
          {
            name: 'Enabled for whitelisted domains',
            value: 'whitelisted'
          },
          {
            name: 'Disabled',
            value: false
          }
        ]
      },
      {
        type: 'input',
        name: 'corsWhitelist',
        message: 'Comma-separated domains for CORS whitelist. Include http(s)',
        when: function(props){
          return props.cors === 'whitelisted';
        }
      },
      {
        type: 'list',
        name: 'database',
        message: 'What database do you primarily want to use?',
        choices: [
          {
            name: 'I will choose my own',
            checked: true
          },
          {
            name: 'Memory',
            value: 'memory'
          },
          {
            name: 'MongoDB',
            value: 'mongodb'
          },
          {
            name: 'MySQL',
            value: 'mysql'
          },
          {
            name: 'MariaDB',
            value: 'mariadb'
          },
          {
            name: 'NeDB',
            value: 'nedb'
          },
          {
            name: 'PostgreSQL',
            value: 'postgres'
          },
          {
            name: 'SQLite',
            value: 'sqlite'
          },
          {
           name: 'SQL Server',
           value: 'mssql'
          }
        ]
      },
      {
        type: 'checkbox',
        name: 'authentication',
        message: 'What authentication methods would you like to support?',
        choices: [
          {
            name: 'local',
            checked: true
          }
        //   name: 'basic'
        // }, {

        // }, {
        //   name: 'google'
        // }, {
        //   name: 'facebook'
        // }, {
        //   name: 'twitter'
        // }, {
        //   name: 'github'
        ]
      }
    ];

    this.prompt(prompts, function (props) {
      this.props = Object.assign(this.props, props);

      done();
    }.bind(this));
  },

  writing: {
    providers: function() {
      if (this.props.providers.indexOf('rest') !== -1) {
        this.dependencies.push('body-parser');
        this.dependencies.push('feathers-rest');
      }

      if (this.props.providers.indexOf('socket.io') !== -1) {
        this.dependencies.push('feathers-socketio');
      }

      if (this.props.providers.indexOf('primus') !== -1) {
        this.dependencies.push('feathers-primus');
        this.dependencies.push('sockjs');
      }
    },

    cors: function() {
      this.props.corsWhitelist = this.props.corsWhitelist && this.props.corsWhitelist.split(',');

      if (this.props.cors) {
        this.dependencies.push('cors');
      }
    },

    authentication: function() {
      this.props.secret = crypto.randomBytes(64).toString('base64');

      if (this.props.authentication.length) {
        this.dependencies.push('feathers-authentication');
      }
    },

    databases: function() {
      switch(this.props.database) {
        case 'memory':
          this.dependencies.push('feathers-memory');
          break;
        case 'mongodb':
          this.dependencies.push('mongoose');
          this.dependencies.push('feathers-mongoose');
          break;
        case 'mysql':
        case 'mariadb':
          this.dependencies.push('mysql');
          this.dependencies.push('sequelize');
          this.dependencies.push('feathers-sequelize');
          break;
        case 'nedb':
          this.dependencies.push('nedb');
          this.dependencies.push('feathers-nedb');
          break;
        case 'postgres':
          this.dependencies.push('pg');
          this.dependencies.push('pg-hstore');
          this.dependencies.push('sequelize');
          this.dependencies.push('feathers-sequelize');
          break;
        case 'sqlite':
          this.dependencies.push('sqlite3');
          this.dependencies.push('fs-extra');
          this.dependencies.push('sequelize');
          this.dependencies.push('feathers-sequelize');
          break;
        case 'mssql':
          this.dependencies.push('tedious');
          this.dependencies.push('sequelize');
          this.dependencies.push('feathers-sequelize');
          break;
      }
    },

    services: function() {
      if (this.props.database) {
        this.composeWith('feathers:service', {
          options: {
            type: 'database',
            database: this.props.database,
            name: 'user',
            hazVersions: true,
            version: 'v1'
          }
        });

        this.fs.copyTpl(
          this.templatePath('service.js'),
          this.destinationPath('server/services', 'index.js'),
          this.props
        );
      }
    },

    application: function() {
      this.fs.copy(this.templatePath('static'), this.destinationPath());
      this.fs.copy(this.templatePath('static/.*'), this.destinationPath());

      this.fs.copyTpl(
        this.templatePath('app.js'),
        this.destinationPath('server', 'app.js'),
        this.props
      );
    },

    middleware: function() {
      this.fs.copyTpl(
        this.templatePath('middleware.js'),
        this.destinationPath('server/middleware', 'index.js'),
        this.props
      );
    },

    config: function() {
      this.fs.copyTpl(
        this.templatePath('config.default.json'),
        this.destinationPath('config', 'default.json'),
        this.props
      );

      this.fs.copyTpl(
        this.templatePath('config.production.json'),
        this.destinationPath('config', 'production.json'),
        this.props
      );

      this.fs.copyTpl(
        this.templatePath('package.json'),
        this.destinationPath('package.json'),
        this.props
      );
    },

    deps: function() {
      this.npmInstall(this.dependencies, { save: true });

      this.npmInstall([
        'jshint',
        'mocha',
        'request'
      ], { saveDev: true});
    }
  }
});
<|endoftext|>


<commit_before>import { log } from './lib/log'

class Animal {
  // constructor
  constructor(name = 'Greedo') {
    this.name = name;
  }

  move() {
    log('PROTOTYPE: I am roaming the earth on my little feet...')
  }

  greet() {
    log(`PROTOTYPE: Hello my name is ${this.name}`)
  }

  static id() {
    log('STATIC: I am a static wild and dangerous animal', 'error')
  }

}

// two syntaxes
const Pony = class extends Animal {
  move() {
    log('I am roaming the earth on my little hooves', 'success')
  }
}

class Dog extends Animal {
  move() {
    log('I am roaming the earth on my little paws', 'success')
  }

  static id() {
    log('STATIC: I am a domesticated animal', 'error')
  }
}

document.querySelector('#app').insertAdjacentHTML('beforeend', '

')

const hank = new Pony('Hank')
const eddy = new Dog('Eddy')

// prototype methods
hank.greet()
eddy.greet()

// overwritten methods
hank.move()
eddy.move()

// static functions
Animal.id()
Dog.id()
<commit_msg>Polish<commit_after>import { log } from './lib/log'

document.querySelector('#app').insertAdjacentHTML('beforeend', '

')

class Animal {
  // constructor
  constructor(name = 'Greedo') {
    this.name = name;
  }

  move() {
    log('PROTOTYPE: I am roaming the earth on my little feet...')
  }

  greet() {
    log(`PROTOTYPE: Hello my name is ${this.name.toUpperCase()}`)
  }

  static id() {
    log('STATIC: I am a static wild and dangerous animal', 'error')
  }

}

// two syntaxes
const Pony = class extends Animal {
  move() {
    log('I am roaming the earth on my little hooves', 'success')
  }
}

class Dog extends Animal {
  move() {
    log('I am roaming the earth on my little paws', 'success')
  }

  static id() {
    log('STATIC: I am a domesticated animal', 'error')
  }
}

const hank = new Pony('Hank')
const eddy = new Dog('Eddy')

// prototype methods
hank.greet()
eddy.greet()

// overwritten methods
hank.move()
eddy.move()

// static functions
Animal.id()
Dog.id()
<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var yosay = require('yosay');
var chalk = require('chalk');

var JadestylGenerator = yeoman.generators.Base.extend({
  init: function () {
    this.pkg = require('../package.json');

    this.on('end', function () {
      if (!this.options['skip-install']) {
        this.installDependencies();
      }
    });
  },

  askFor: function () {
    var done = this.async();

    // Have Yeoman greet the user.
    this.log(yosay('Welcome to the marvelous Jadestyl generator!'));

    var prompts = [{
      name: 'title',
      message: 'What is the title of your application?',
      default: 'Hello-World'
    }];

    this.prompt(prompts, function (props) {
      this.title = props.title;

      done();
    }.bind(this));
  },

  app: function () {
    this.mkdir('src');
    this.mkdir('src/scripts');
    this.mkdir('src/styles');

    this.template('src/index.jade', 'src/index.jade');
    this.template('src/scripts/main.js', 'src/scripts/main.js');
    this.template('src/styles/main.styl', 'src/styles/main.styl');
    this.copy('_package.json', 'package.json');
    this.copy('_bower.json', 'bower.json');
    this.copy('editorconfig', '.editorconfig');
    this.copy('gitignore', '.gitignore');
    this.copy('bowerrc', '.bowerrc');
    this.copy('jshintrc', '.jshintrc');
    this.template('Gruntfile.js', 'Gruntfile.js');
    this.template('README.md', 'README.md');
  },

  projectfiles: function () {
  }
});

module.exports = JadestylGenerator;
<commit_msg>Update index.js<commit_after>'use strict';
var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var yosay = require('yosay');
var chalk = require('chalk');

var JadestylGenerator = yeoman.generators.Base.extend({
  init: function () {
    this.pkg = require('../package.json');

    this.on('end', function () {
      if (!this.options['skip-install']) {
        this.installDependencies();
      }
    });
  },

  askFor: function () {
    var done = this.async();

    // Have Yeoman greet the user.
    this.log(yosay('Welcome to the marvelous Jadestyl generator!'));

    var prompts = [{
      name: 'title',
      message: 'What is the title of your application?',
      default: 'Hello World'
    }];

    this.prompt(prompts, function (props) {
      this.title = props.title;

      done();

  var extractGeneratorName = function (_, appname) {
    var slugged = _.slugify(title);
    var match = slugged.match(/^$/);

    if (match && match.length === 2) {
      return match[1].toLowerCase();
  }

  return slugged;
  };
    }.bind(this));
  },

  app: function () {
    this.mkdir('src');
    this.mkdir('src/scripts');
    this.mkdir('src/styles');

    this.template('src/index.jade', 'src/index.jade');
    this.template('src/scripts/main.js', 'src/scripts/main.js');
    this.template('src/styles/main.styl', 'src/styles/main.styl');
    this.copy('_package.json', 'package.json');
    this.copy('_bower.json', 'bower.json');
    this.copy('editorconfig', '.editorconfig');
    this.copy('gitignore', '.gitignore');
    this.copy('bowerrc', '.bowerrc');
    this.copy('jshintrc', '.jshintrc');
    this.template('Gruntfile.js', 'Gruntfile.js');
    this.template('README.md', 'README.md');
  },

  projectfiles: function () {
  }
});

module.exports = JadestylGenerator;
<|endoftext|>


<commit_before>import {getApi} from '../common/oauth';
var setting = JSON.parse(localStorage["ulttwiclient"]);
console.log(setting.tokens);

function createTweetDom(tweet, api){
  var dom_tweet = document.createElement("li");
  var text_tweet = document.createElement("span");
  var favorite_marker = document.createElement("span");

  text_tweet.textContent = tweet.text;
  favorite_marker.textContent = (tweet.favorited ? "🍣" : "🍚") + tweet.favorite_count;
  favorite_marker.addEventListener('click', ()=>{
      console.log(tweet.id_str);
      var favorites_url = tweet.favorited ? 'favorites/destroy' : 'favorites/create';
    api.post(favorites_url, {id: tweet.id_str}, (error, _tweet, response)=>{
      if (!error) {
        console.log(_tweet, response);
        tweet.favorited = !tweet.favorited;
        favorite_marker.textContent = (_tweet.favorited ? "🍣" : "🍚") + (_tweet.favorite_count);
      } else {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
    });
  });

  dom_tweet.appendChild(text_tweet);
  dom_tweet.appendChild(favorite_marker);
  return dom_tweet;
}
window.addEventListener('load',()=>{
  if(setting.tokens){
    var api = getApi(setting.tokens);
    var params = {screen_name: 'wass80'};
    var ul_tweet = document.getElementById("tweets");
    var me;
    api.get('account/verify_credentials', {}, (error, data, response)=>{
      if (!error) {
        me = data;
      } else {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
    });
    api.get('statuses/home_timeline', params, function(error, tweets, response){
      if (!error) {
        console.log('tweets',tweets.map((t)=>t.text).join("\n"), tweets);
        tweets.forEach((tweet)=>{
          ul_tweet.appendChild(createTweetDom(tweet, api));
        });
      } else {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
    });
    var inp_submitbox = document.getElementById("submitbox");
    var btn_submitbtn = document.getElementById("submitbtn");
    var btn_streambtn = document.getElementById("streambtn");
    btn_submitbtn.addEventListener('click',()=>{
      console.info(inp_submitbox.value);
      api.post('statuses/update', {
          status: inp_submitbox.value
        },(error, tweet, response)=>{
        if (!error) {
          console.log(tweet, response);
        } else {
          console.log('error', error.map((e)=>e.message).join("\n"),  error);
        }
      });
    });
    btn_streambtn.addEventListener('click',()=>{
      console.info("start streaming");
      api.stream('user', {}, (stream)=>{
        stream.on('data', (tweet)=>{
          if (!tweet.friends) {
            console.log(tweet);
            ul_tweet.insertBefore(createTweetDom(tweet, api), ul_tweet.firstChild);
          }
        });
        stream.on('favorite', (data)=>{
          if (data.target.screen_name === me.screen_name) {
            var favoriteNotification = new Notification("あなたのツイートがいいねされました", {
              body: data.target_object.text
            });
          }
        });
      });
    });
  }else{
    console.warn("did not oauth");
  }
});
<commit_msg>いいね取り消し通知<commit_after>import {getApi} from '../common/oauth';
var setting = JSON.parse(localStorage["ulttwiclient"]);
console.log(setting.tokens);

function createTweetDom(tweet, api){
  var dom_tweet = document.createElement("li");
  var text_tweet = document.createElement("span");
  var favorite_marker = document.createElement("span");

  text_tweet.textContent = tweet.text;
  favorite_marker.textContent = (tweet.favorited ? "🍣" : "🍚") + tweet.favorite_count;
  favorite_marker.addEventListener('click', ()=>{
      console.log(tweet.id_str);
      var favorites_url = tweet.favorited ? 'favorites/destroy' : 'favorites/create';
    api.post(favorites_url, {id: tweet.id_str}, (error, _tweet, response)=>{
      if (!error) {
        console.log(_tweet, response);
        tweet.favorited = !tweet.favorited;
        favorite_marker.textContent = (_tweet.favorited ? "🍣" : "🍚") + (_tweet.favorite_count);
      } else {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
    });
  });

  dom_tweet.appendChild(text_tweet);
  dom_tweet.appendChild(favorite_marker);
  return dom_tweet;
}
window.addEventListener('load',()=>{
  if(setting.tokens){
    var api = getApi(setting.tokens);
    var params = {screen_name: 'wass80'};
    var ul_tweet = document.getElementById("tweets");
    var me;
    api.get('account/verify_credentials', {}, (error, data, response)=>{
      if (!error) {
        me = data;
      } else {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
    });
    api.get('statuses/home_timeline', params, function(error, tweets, response){
      if (!error) {
        console.log('tweets',tweets.map((t)=>t.text).join("\n"), tweets);
        tweets.forEach((tweet)=>{
          ul_tweet.appendChild(createTweetDom(tweet, api));
        });
      } else {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
    });
    var inp_submitbox = document.getElementById("submitbox");
    var btn_submitbtn = document.getElementById("submitbtn");
    var btn_streambtn = document.getElementById("streambtn");
    btn_submitbtn.addEventListener('click',()=>{
      console.info(inp_submitbox.value);
      api.post('statuses/update', {
          status: inp_submitbox.value
        },(error, tweet, response)=>{
        if (!error) {
          console.log(tweet, response);
        } else {
          console.log('error', error.map((e)=>e.message).join("\n"),  error);
        }
      });
    });
    btn_streambtn.addEventListener('click',()=>{
      console.info("start streaming");
      api.stream('user', {}, (stream)=>{
        stream.on('data', (tweet)=>{
          if (!tweet.friends) {
            console.log(tweet);
            ul_tweet.insertBefore(createTweetDom(tweet, api), ul_tweet.firstChild);
          }
        });
        stream.on('favorite', (data)=>{
          if (data.target.screen_name === me.screen_name) {
            var favoriteNotification = new Notification("あなたのツイートがいいねされました", {
              body: data.target_object.text
            });
          }
        });
        stream.on('unfavorite', (data)=>{
          if (data.target.screen_name === me.screen_name) {
            var favoriteNotification = new Notification("あなたのツイートがいいね取り消しされました", {
              body: data.target_object.text
            });
          }
        });
      });
    });
  }else{
    console.warn("did not oauth");
  }
});
<|endoftext|>


<commit_before>// Run with mocha by installing dev deps: npm install --dev
// more docs on writing tests with mocha can be found here:
// http://visionmedia.github.com/mocha/
/*global describe:true, it:true */
"use strict";

var should = require('should')
  , _ = require('underscore')
  , au = require('../../uiauto/appium/xpath.js');

describe("XPath lookups", function() {
  var oks = {
    "//button": {absolute: true, path: ['button']}
    , "/button": {absolute: false, path: ['button']}
    , "button": {absolute: false, path: ['button']}
    , "//button/text/webview": {
        absolute: true, path: ['button', 'text', 'webview']}
    , "text/webview/button": {
        absolute: false, path: ['text', 'webview', 'button']}
    , "//button[@name='hi there']": {
        attr: 'name', constraint: 'hi there', substr: false}
    , "//button[@other_attr='hi there']": {
        attr: 'other_attr', constraint: 'hi there', substr: false}
    , '//button[@name="hi there"]': {
        attr: 'name', constraint: 'hi there', substr: false}
    , '//button[@name=hi there]': {
        attr: 'name', constraint: 'hi there', substr: false}
    , '//button[contains(@label, "hi")]': {
        attr: 'label', constraint: 'hi', substr: true}
    , '//button[contains(@other_attr, "hi")]': {
        attr: 'other_attr', constraint: 'hi', substr: true}
    , "//button[contains(@label, 'hi')]": {
        attr: 'label', constraint: 'hi', substr: true}
    , "//button[contains(@label, what's up dog)]": {
        attr: 'label', constraint: "what's up dog", substr: true}
  };
  var notOks = [
    , "//button123"
    , "//button[@name$='hi']"
    , "//tag_name"
    , "//button//lol"
    , "//button[something(@name, 'hi')]"
    , "//button[noat='wut']"
    , "//button/label[@name='hi']/moar"
  ];
  _.each(oks, function(test, xpath) {
    it(xpath + " should work", function() {
      var parsed = au.parseXpath(xpath);
      parsed.should.not.equal(false);
      _.each(test, function(val, key) {
        parsed[key].should.eql(test[key]);
      });
    });
  });
  _.each(notOks, function(xpath) {
    it(xpath + " should not work", function() {
      var parsed = au.parseXpath(xpath);
      parsed.should.equal(false);
    });
  });
});
<commit_msg>test organization<commit_after>// Run with mocha by installing dev deps: npm install --dev
// more docs on writing tests with mocha can be found here:
// http://visionmedia.github.com/mocha/
/*global describe:true, it:true */
"use strict";

var should = require('should')
  , _ = require('underscore')
  , au = require('../../uiauto/appium/xpath.js');

describe("XPath lookups", function() {
  var oks = {
    "//button": {absolute: true, path: ['button']}
    , "/button": {absolute: false, path: ['button']}
    , "button": {absolute: false, path: ['button']}
    , "//button/text/webview": {
        absolute: true, path: ['button', 'text', 'webview']}
    , "text/webview/button": {
        absolute: false, path: ['text', 'webview', 'button']}
    , "//button[@name='hi there']": {
        attr: 'name', constraint: 'hi there', substr: false}
    , "//button[@other_attr='hi there']": {
        attr: 'other_attr', constraint: 'hi there', substr: false}
    , '//button[@name="hi there"]': {
        attr: 'name', constraint: 'hi there', substr: false}
    , '//button[@name=hi there]': {
        attr: 'name', constraint: 'hi there', substr: false}
    , '//button[contains(@label, "hi")]': {
        attr: 'label', constraint: 'hi', substr: true}
    , '//button[contains(@other_attr, "hi")]': {
        attr: 'other_attr', constraint: 'hi', substr: true}
    , "//button[contains(@label, 'hi')]": {
        attr: 'label', constraint: 'hi', substr: true}
    , "//button[contains(@label, what's up dog)]": {
        attr: 'label', constraint: "what's up dog", substr: true}
  };
  var notOks = [
    , "//button123"
    , "//button[@name$='hi']"
    , "//tag_name"
    , "//button//lol"
    , "//button[something(@name, 'hi')]"
    , "//button[noat='wut']"
    , "//button/label[@name='hi']/moar"
  ];
  describe("Valid XPaths", function() {
    _.each(oks, function(test, xpath) {
      it(xpath + " should work", function() {
        var parsed = au.parseXpath(xpath);
        parsed.should.not.equal(false);
        _.each(test, function(val, key) {
          parsed[key].should.eql(test[key]);
        });
      });
    });
  });
  describe("Invalid Xpaths", function() {
    _.each(notOks, function(xpath) {
      it(xpath + " should not work", function() {
        var parsed = au.parseXpath(xpath);
        parsed.should.equal(false);
      });
    });
  });
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  StyleGuideProvider,
  Header,
  Footer,
  PageBlock,
  Card,
  Section,
  Text,
  TextField,
  Checkbox
} from 'seek-style-guide/react';
import styles from './App.less';

export default class App extends Component {
  constructor() {
    super();

    this.state = {
      name: '',
      showMessage: false
    };
  }

  handleChange = ({ target }) => {
    this.setState({
      [target.name]: target.type === 'checkbox' ? target.checked : target.value
    });
  };

  render() {
    return (

    );
  }
}
<commit_msg>fix: Remove `inputProps` usage in App template (#144)<commit_after>import React, { Component } from 'react';
import {
  StyleGuideProvider,
  Header,
  Footer,
  PageBlock,
  Card,
  Section,
  Text,
  TextField,
  Checkbox
} from 'seek-style-guide/react';
import styles from './App.less';

export default class App extends Component {
  constructor() {
    super();

    this.state = {
      name: '',
      showMessage: false
    };
  }

  handleChange = ({ target }) => {
    this.setState({
      [target.name]: target.type === 'checkbox' ? target.checked : target.value
    });
  };

  render() {
    return (

    );
  }
}
<|endoftext|>