<commit_before>'use strict';

var parseArgs = require('minimist');
var process = require('process');
var assert = require('assert');
var readJSON = require('read-json');
var console = require('console');
var path = require('path');
var os = require('os');
var DebugLogtron = require('debug-logtron');
var globalTimers = require('timers');
var EventEmitter = require('events').EventEmitter;
var util = require('util');

var ThriftRepository = require('../thrift-repository.js');

/*eslint no-process-env: 0*/
var HOME = process.env.HOME;

module.exports = ThriftGod;

if (require.main === module) {
    var argv = parseArgs(process.argv.slice(2));
    var thriftGod = ThriftGod(argv);
    thriftGod.on('error', function onRemote(err) {
        console.error('ERR: ', err);
        process.exit(1);
    });
    thriftGod.bootstrap(function onFini(err) {
        if (err) {
            console.error('ERR: ', err);
            process.exit(1);
        }
    });
}

/*eslint no-console: 0, no-process-exit: 0 */
function ThriftGod(opts) {
    if (!(this instanceof ThriftGod)) {
        return new ThriftGod(opts);
    }

    var self = this;
    EventEmitter.call(self);

    self.opts = opts;
    if (opts.h || opts.help) {
        return self.help();
    }

    self.logger = opts.logger || DebugLogtron('thriftgod');
    self.timers = opts.timers || globalTimers;
    self.configFile = opts['config-file'] || opts.configFile;
    assert(self.configFile, '--config-file is required');

    self.config = null;
    self.thriftRepo = null;
    self.timer = null;
}
util.inherits(ThriftGod, EventEmitter);

ThriftGod.prototype.bootstrap = function bootstrap(cb) {
    var self = this;

    // TODO reload json file when it changes
    readJSON(self.configFile, onConfig);

    function onConfig(err, data) {
        if (err) {
            return cb(err);
        }

        self.config = ThriftGodConfig(data);
        self.thriftRepo = ThriftRepository({
            remotes: self.config.remotes,
            upstream: self.config.upstream,
            repositoryFolder: self.config.repositoryFolder,
            cacheLocation: self.config.cacheLocation,
            logger: self.logger
        });

        self.thriftRepo.bootstrap(onBootstrap);
    }

    function onBootstrap(err) {
        if (err) {
            return cb(err);
        }

        self.emit('fetchedRemotes');
        self.repeat();
        cb(null);
    }
};

ThriftGod.prototype.repeat = function repeat() {
    var self = this;

    self.timers.setTimeout(
        fetchRemotes, self.config.fetchInterval
    );

    function fetchRemotes() {
        self.thriftRepo.fetchRemotes(onRemote);
    }

    function onRemote(err) {
        if (err) {
            return self.emit('error', err);
        }

        self.emit('fetchedRemotes');
        self.repeat();
    }
};

ThriftGod.prototype.help = function help() {
    console.log('usage: thrift-god [--help] [-h]');
    console.log('                  --config-file=<file>');
};

ThriftGod.prototype.destroy = function destroy() {
    var self = this;

    self.timers.clearTimeout(self.timer);
};

function ThriftGodConfig(data) {
    if (!(this instanceof ThriftGodConfig)) {
        return new ThriftGodConfig(data);
    }

    var self = this;

    assert(typeof data === 'object' && data,
        'config file must be a json object');
    assert(data.remotes && Array.isArray(data.remotes),
        'must configure `remotes`');
    assert(data.upstream && typeof data.upstream === 'string',
        'must configure `upstream`');
    assert(data.fileNameStrategy &&
        typeof data.fileNameStrategy === 'string',
        'must configure fileNameStrategy');
    assert(data.fetchInterval &&
        typeof data.fetchInterval === 'number',
        'must configure fetchInterval');

    self.fileNameStrategy = data.fileNameStrategy;
    self.upstream = data.upstream;
    self.fetchInterval = data.fetchInterval;

    self.repositoryFolder = data.repositoryFolder || path.join(
        os.tmpDir(), 'thrift-god', new Date().toISOString()
    );
    self.cacheLocation = data.cacheLocation || path.join(
        HOME, '.thrift-god', 'remote-cache'
    );

    self.remotes = [];
    for (var i = 0; i < data.remotes.length; i++) {
        var remote = data.remotes[i];
        self.remotes.push(ThriftRemote({
            repository: remote.repository,
            branch: remote.branch,
            localFileName: remote.localFileName,
            strategy: self.fileNameStrategy
        }));
    }
}

function ThriftRemote(opts) {
    if (!(this instanceof ThriftRemote)) {
        return new ThriftRemote(opts);
    }

    var self = this;

    assert(opts.repository, 'opts.repository required');
    assert(opts.strategy, 'opts.strategy required');

    self.repository = opts.repository;
    self.branch = opts.branch || 'master';
    self.localFileName = opts.localFileName ||
        'thrift/service.thrift';
    self.folderName = null;

    var parts;
    if (opts.strategy === 'lastSegment') {
        parts = self.repository.split('/');
        self.folderName = parts[parts.length - 1];
    // TODO: test lastTwoSegments strategy
    } else if (opts.strategy === 'lastTwoSegments') {
        parts = self.repository.split('/');
        self.folderName = path.join(
            parts[parts.length - 2],
            parts[parts.length - 1]
        );
    // TODO: test splitOnColon strategy
    } else if (opts.strategy === 'splitOnColon') {
        parts = self.repository.split(':');
        self.folderName = parts[parts.length - 1];
    }

    self.fileName = self.folderName + '.thrift';

}
<commit_msg>correct order of stuff<commit_after>'use strict';

var parseArgs = require('minimist');
var process = require('process');
var assert = require('assert');
var readJSON = require('read-json');
var console = require('console');
var path = require('path');
var os = require('os');
var DebugLogtron = require('debug-logtron');
var globalTimers = require('timers');
var EventEmitter = require('events').EventEmitter;
var util = require('util');

var ThriftRepository = require('../thrift-repository.js');

/*eslint no-process-env: 0*/
var HOME = process.env.HOME;

module.exports = ThriftGod;

function main() {
    var argv = parseArgs(process.argv.slice(2));
    var thriftGod = ThriftGod(argv);
    thriftGod.on('error', function onRemote(err) {
        console.error('ERR: ', err);
        process.exit(1);
    });
    thriftGod.bootstrap(function onFini(err) {
        if (err) {
            console.error('ERR: ', err);
            process.exit(1);
        }
    });
}

/*eslint no-console: 0, no-process-exit: 0 */
function ThriftGod(opts) {
    if (!(this instanceof ThriftGod)) {
        return new ThriftGod(opts);
    }

    var self = this;
    EventEmitter.call(self);

    self.opts = opts;
    if (opts.h || opts.help) {
        return self.help();
    }

    self.logger = opts.logger || DebugLogtron('thriftgod');
    self.timers = opts.timers || globalTimers;
    self.configFile = opts['config-file'] || opts.configFile;
    assert(self.configFile, '--config-file is required');

    self.config = null;
    self.thriftRepo = null;
    self.timer = null;
}
util.inherits(ThriftGod, EventEmitter);

ThriftGod.prototype.bootstrap = function bootstrap(cb) {
    var self = this;

    // TODO reload json file when it changes
    readJSON(self.configFile, onConfig);

    function onConfig(err, data) {
        if (err) {
            return cb(err);
        }

        self.config = ThriftGodConfig(data);
        self.thriftRepo = ThriftRepository({
            remotes: self.config.remotes,
            upstream: self.config.upstream,
            repositoryFolder: self.config.repositoryFolder,
            cacheLocation: self.config.cacheLocation,
            logger: self.logger
        });

        self.thriftRepo.bootstrap(onBootstrap);
    }

    function onBootstrap(err) {
        if (err) {
            return cb(err);
        }

        self.emit('fetchedRemotes');
        self.repeat();
        cb(null);
    }
};

ThriftGod.prototype.repeat = function repeat() {
    var self = this;

    self.timers.setTimeout(
        fetchRemotes, self.config.fetchInterval
    );

    function fetchRemotes() {
        self.thriftRepo.fetchRemotes(onRemote);
    }

    function onRemote(err) {
        if (err) {
            return self.emit('error', err);
        }

        self.emit('fetchedRemotes');
        self.repeat();
    }
};

ThriftGod.prototype.help = function help() {
    console.log('usage: thrift-god [--help] [-h]');
    console.log('                  --config-file=<file>');
};

ThriftGod.prototype.destroy = function destroy() {
    var self = this;

    self.timers.clearTimeout(self.timer);
};

function ThriftGodConfig(data) {
    if (!(this instanceof ThriftGodConfig)) {
        return new ThriftGodConfig(data);
    }

    var self = this;

    assert(typeof data === 'object' && data,
        'config file must be a json object');
    assert(data.remotes && Array.isArray(data.remotes),
        'must configure `remotes`');
    assert(data.upstream && typeof data.upstream === 'string',
        'must configure `upstream`');
    assert(data.fileNameStrategy &&
        typeof data.fileNameStrategy === 'string',
        'must configure fileNameStrategy');
    assert(data.fetchInterval &&
        typeof data.fetchInterval === 'number',
        'must configure fetchInterval');

    self.fileNameStrategy = data.fileNameStrategy;
    self.upstream = data.upstream;
    self.fetchInterval = data.fetchInterval;

    self.repositoryFolder = data.repositoryFolder || path.join(
        os.tmpDir(), 'thrift-god', new Date().toISOString()
    );
    self.cacheLocation = data.cacheLocation || path.join(
        HOME, '.thrift-god', 'remote-cache'
    );

    self.remotes = [];
    for (var i = 0; i < data.remotes.length; i++) {
        var remote = data.remotes[i];
        self.remotes.push(ThriftRemote({
            repository: remote.repository,
            branch: remote.branch,
            localFileName: remote.localFileName,
            strategy: self.fileNameStrategy
        }));
    }
}

function ThriftRemote(opts) {
    if (!(this instanceof ThriftRemote)) {
        return new ThriftRemote(opts);
    }

    var self = this;

    assert(opts.repository, 'opts.repository required');
    assert(opts.strategy, 'opts.strategy required');

    self.repository = opts.repository;
    self.branch = opts.branch || 'master';
    self.localFileName = opts.localFileName ||
        'thrift/service.thrift';
    self.folderName = null;

    var parts;
    if (opts.strategy === 'lastSegment') {
        parts = self.repository.split('/');
        self.folderName = parts[parts.length - 1];
    // TODO: test lastTwoSegments strategy
    } else if (opts.strategy === 'lastTwoSegments') {
        parts = self.repository.split('/');
        self.folderName = path.join(
            parts[parts.length - 2],
            parts[parts.length - 1]
        );
    // TODO: test splitOnColon strategy
    } else if (opts.strategy === 'splitOnColon') {
        parts = self.repository.split(':');
        self.folderName = parts[parts.length - 1];
    }

    self.fileName = self.folderName + '.thrift';
}

if (require.main === module) {
    main();
}
<|endoftext|>


<commit_before>// Initialize app
var myApp = new Framework7();

// If we need to use custom DOM library, let's save it to $$ variable:
var $$ = Dom7;

// Add view
var mainView = myApp.addView('.view-main', {
    // Because we want to use dynamic navbar, we need to enable it for this view:
    dynamicNavbar: true
});

var currentpage = "page1";
var pages = ["page1", "page2", "page3", "page4"];

function setPage(page){
  currentpage = page;
  render();
}

function renderPages(){
  disableAllPages();
  document.getElementById(currentpage).style.display = 'flex';
  console.log(currentpage + " rendered");
}

function disableAllPages(){
  for (i = 0; i < pages.length; i++){
    console.log("Disabled page:", pages[i]);
    document.getElementById(pages[i]).style.display = 'none';
  }
}

$(document).ready( () => {
    console.log("Document ready");
    document.getElementById("page1Btn").addEventListener("click", setPage("page1"));
    document.getElementById("page2Btn").addEventListener("click", setPage("page2"));
    document.getElementById("page3Btn").addEventListener("click", setPage("page3"));
    document.getElementById("page4Btn").addEventListener("click", setPage("page4"));

    disableAllPages();
    renderPages();
});

// Handle Cordova Device Ready Event
$$(document).on('deviceready', function() {
    console.log("Device is ready!");
    disableAllPages();
    renderPages();
});

// Now we need to run the code that will be executed only for About page.

// Option 1. Using page callback for page (for "about" page in this case) (recommended way):
myApp.onPageInit('about', function (page) {
    // Do something here for "about" page

})

// Option 2. Using one 'pageInit' event handler for all pages:
$$(document).on('pageInit', function (e) {
    // Get page data from event data
    var page = e.detail.page;

    if (page.name === 'about') {
        // Following code will be executed for page with data-page attribute equal to "about"
        myApp.alert('Here comes About page');
    }
})

// Option 2. Using live 'pageInit' event handlers for each page
$$(document).on('pageInit', '.page[data-page="about"]', function (e) {
    // Following code will be executed for page with data-page attribute equal to "about"
    myApp.alert('Here comes About page');
})
<commit_msg>Fixed mini-router<commit_after>// Initialize app
var myApp = new Framework7();

// If we need to use custom DOM library, let's save it to $$ variable:
var $$ = Dom7;

// Add view
var mainView = myApp.addView('.view-main', {
    // Because we want to use dynamic navbar, we need to enable it for this view:
    dynamicNavbar: true
});

var currentpage = "page1";
var pages = ["page1", "page2", "page3", "page4"];

function setPage(page){
  currentpage = page;
  renderPages();
}

function renderPages(){
  disableAllPages();
  document.getElementById(currentpage).style.display = 'flex';
  console.log(currentpage + " rendered");
}

function disableAllPages(){
  for (i = 0; i < pages.length; i++){
    console.log("Disabled page:", pages[i]);
    document.getElementById(pages[i]).style.display = 'none';
  }
}

$(document).ready( () => {
    console.log("Document ready");
    document.getElementById("page1Btn").addEventListener("click", setPage("page1"));
    document.getElementById("page2Btn").addEventListener("click", setPage("page2"));
    document.getElementById("page3Btn").addEventListener("click", setPage("page3"));
    document.getElementById("page4Btn").addEventListener("click", setPage("page4"));

    disableAllPages();
    renderPages();
});

// Handle Cordova Device Ready Event
$$(document).on('deviceready', function() {
    console.log("Device is ready!");
    disableAllPages();
    renderPages();
});

// Now we need to run the code that will be executed only for About page.

// Option 1. Using page callback for page (for "about" page in this case) (recommended way):
myApp.onPageInit('about', function (page) {
    // Do something here for "about" page

})

// Option 2. Using one 'pageInit' event handler for all pages:
$$(document).on('pageInit', function (e) {
    // Get page data from event data
    var page = e.detail.page;

    if (page.name === 'about') {
        // Following code will be executed for page with data-page attribute equal to "about"
        myApp.alert('Here comes About page');
    }
})

// Option 2. Using live 'pageInit' event handlers for each page
$$(document).on('pageInit', '.page[data-page="about"]', function (e) {
    // Following code will be executed for page with data-page attribute equal to "about"
    myApp.alert('Here comes About page');
})
<|endoftext|>


<commit_before>import {getApi} from '../common/oauth';
const remote = require('electron').remote;
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;
const clipboard = require('electron').clipboard;
const Vue = require('vue');
var setting = JSON.parse(localStorage["ulttwiclient"]);
console.log(setting.tokens);

window.addEventListener('load',()=>{
  Vue.filter('expand_url', function (text, entities){
    entities.urls.forEach((url)=>{
      text = text.replace(url.url, url.display_url);
    });
    if (entities.media) {
      entities.media.forEach((m)=>{
        text = text.replace(m.url, m.display_url);
      });
    }
    return text;
  });
  // context menu on status
  var contextMenuForTweet = Menu.buildFromTemplate([
    {label: "ツイートに返信する", click: ()=>{vm.sendReply(vm.selectedTweet);}},
    {label: "ツイートをふぁぼる", click: ()=>{vm.favoriteTweet(vm.selectedTweet);}},
    {type: 'separator'},
    {label: "ツイートのJSONを取得", click: ()=>{clipboard.writeText(JSON.stringify(vm.selectedTweet));}}
  ]);
  var vm = new Vue({
    el: "#container",
    data: {
      newTweet: {
        text: "",
        in_reply_to_status_id: ""
      },
      selectedTweet: {},
      me: {},
      streaming: false,
      showingImage: false,
      image: {},
      tweets: [],
      notifications: [],
      maxTweetLength: 140,
      borderOfLongTweet: 20
    },
    computed: {
      calculateRemainChar: function() {
        return this.maxTweetLength - this.newTweet.text.length;
      },
      isLongTweet: function() {
        return this.borderOfLongTweet > this.calculateRemainChar;
      },
      isExceededTweet: function() {
        return this.calculateRemainChar < 0;
      }
    },
    methods: {
      sendTweet: function (params) {
        api.post('statuses/update', {
          status: params.text,
          in_reply_to_status_id: params.in_reply_to_status_id
        }, (error, tweet, response)=>{
          if (error) {
            this.createNotification("ツイートの投稿に失敗しました", this.newTweet.text, null, 'fail');
            return console.log('error', error.map((e)=>e.message).join("\n"),  error);
          }
          console.log(tweet, response);
          this.createNotification("ツイートが投稿されました", this.newTweet.text, null, 'tweet');
          this.newTweet.text = '';
          this.newTweet.in_reply_to_status_id = '';
        });
      },
      clearReply: function () {
        this.newTweet.text = this.newTweet.text.replace(/@[a-zA-Z0-9_]+/g, '');
        this.newTweet.in_reply_to_status_id = '';
      },
      startStreaming: function () {
        console.info("start streaming");
        this.streaming = true;
        api.stream('user', {}, (stream)=>{
          stream.on('data', (data)=>{
            console.log(data);
            if (data.friends) {
              return;
            } else if (data.delete) {
              console.log(data.delete);
              this.deleteTweet(data.delete.status.id_str);
            } else if (data.scrub_geo) {
              // location deletion
            } else if (data.limit) {
              // limit notices
            } else if (data.status_withheld) {
              // withheld content notices (status)
            } else if (data.user_withheld) {
              // withheld content notices (user)
            } else if (data.disconnect) {
              // disconnect messages
            } else if (data.warning) {
              // stall warnings
            } else if (data.event) {
              // catch some event
            } else {
              // status
              this.tweets.push(data);
              if (this.isMentionsForYou(data)) {
                this.createNotification("あなた宛のメンションがあります", data.text, data.user.profile_image_url_https, 'tweet');
              }
            }
          });
          stream.on('favorite', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification("あなたのツイートがいいねされました", data.target_object.text, data.target.profile_image_url_https, 'favorite');
            }
          });
          stream.on('unfavorite', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification("あなたのツイートがいいね取り消しされました", data.target_object.text, data.target.profile_image_url_https, 'unfavorite');
            }
          });
          stream.on('follow', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification(data.source.name + " さんにフォローされました", data.source.description, data.source.profile_image_url_https, 'follow');
            }
          });
          stream.on('list_member_added', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification(data.source.name + " さんにリスト "+data.target_object.name+" に追加されました", data.target_object.description, data.source.profile_image_url_https, 'list_member_added');
            }
          });
        });
      },
      favoriteTweet: function (tweet) {
        console.log(tweet.id_str);
        var favorites_url = tweet.favorited ? 'favorites/destroy' : 'favorites/create';
        var favorites_id = tweet.retweeted_status ? tweet.retweeted_status.id_str : tweet.id_str
        api.post(favorites_url, {id: favorites_id}, (error, _tweet, response)=>{
          if (error) {
            this.createNotification("ツイートをいいねできませんでした", tweet.text, null, 'fail');
            return console.log('error', error.map((e)=>e.message).join("\n"),  error);
          }
          console.log(_tweet, response);
          if (tweet.favorited)
            this.createNotification("ツイートをいいね取り消ししました", tweet.text, null, 'unfavorite');
          else
            this.createNotification("ツイートをいいねしました", tweet.text, null, 'favorite');
          tweet.favorited = _tweet.favorited;
          tweet.favotite_count = _tweet.favorite_count;
        });
      },
      showImage: function(url, size) {
        this.image = {url: url, width: size.w, height: size.h};
        this.showingImage = true;
      },
      closeImage: function() {
        this.image = {};
        this.showingImage = false;
      },
      sendReply: function(tweet) {
        console.log(tweet.id_str);
        this.newTweet.text = "@" + tweet.user.screen_name + " ";
        this.newTweet.in_reply_to_status_id = tweet.id_str;
        this.$els.submit_box.focus();
      },
      hasMedias: function(tweet) {
        return 'extended_entities' in tweet && 'media' in tweet.extended_entities;
      },
      isMentionsForYou: function(tweet) {
        return tweet.text.match('@' + this.me.screen_name) && !tweet.retweeted_status;
      },
      deleteTweet: function(id_str) {
        this.tweets.forEach((t,i) => {
          if (t.id_str === id_str)
            this.tweets.splice(i, 1);
        })
      },
      createNotification: function(title, body, icon, kind) {
        let notification = {title, body, kind};
        this.notifications.push(notification);
        const removeMSecond = 6000;
        window.setTimeout(()=>{
          this.removeNotification(notification);
        }, removeMSecond);
        if (!remote.getCurrentWindow().isFocused()) {
          new Notification(title, {icon: icon, body: body});
        }
      },
      removeNotification: function(notification) {
        this.notifications.forEach((n,i) => {
          if (n === notification)
            this.notifications.splice(i, 1);
        })
      },
      contextMenuOnTweet: function(tweet) {
        this.selectedTweet = tweet;
        contextMenuForTweet.popup(remote.getCurrentWindow());
      }
    }
  });
  if(setting.tokens){
    var api = getApi(setting.tokens);
    api.get('account/verify_credentials', {}, (error, data, response)=>{
      if (error) {
        return console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
      vm.me = data;
    });
    api.get('statuses/home_timeline', {count: 200}, function(error, tweets, response){
      if (error) {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
      console.log('tweets',tweets.map((t)=>t.text).join("\n"), tweets);
      tweets.forEach((tweet)=>{
        vm.tweets.push(tweet);
      });
    });
    vm.startStreaming();
  }else{
    console.warn("did not oauth");
  }
});
<commit_msg>twitter-text を使用<commit_after>import {getApi} from '../common/oauth';
const remote = require('electron').remote;
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;
const clipboard = require('electron').clipboard;
const Vue = require('vue');
require('twitter-text');
var setting = JSON.parse(localStorage["ulttwiclient"]);
console.log(setting.tokens);

window.addEventListener('load',()=>{
  Vue.filter('expand_url', function (text, entities){
    entities.urls.forEach((url)=>{
      text = text.replace(url.url, url.display_url);
    });
    if (entities.media) {
      entities.media.forEach((m)=>{
        text = text.replace(m.url, m.display_url);
      });
    }
    return text;
  });
  // context menu on status
  var contextMenuForTweet = Menu.buildFromTemplate([
    {label: "ツイートに返信する", click: ()=>{vm.sendReply(vm.selectedTweet);}},
    {label: "ツイートをふぁぼる", click: ()=>{vm.favoriteTweet(vm.selectedTweet);}},
    {type: 'separator'},
    {label: "ツイートのJSONを取得", click: ()=>{clipboard.writeText(JSON.stringify(vm.selectedTweet));}}
  ]);
  var vm = new Vue({
    el: "#container",
    data: {
      newTweet: {
        text: "",
        in_reply_to_status_id: ""
      },
      selectedTweet: {},
      me: {},
      streaming: false,
      showingImage: false,
      image: {},
      tweets: [],
      notifications: [],
      maxTweetLength: 140,
      borderOfLongTweet: 20
    },
    computed: {
      calculateRemainChar: function() {
        return this.maxTweetLength - twttr.txt.getTweetLength(this.newTweet.text);
      },
      isLongTweet: function() {
        return this.borderOfLongTweet > this.calculateRemainChar;
      },
      isExceededTweet: function() {
        return this.calculateRemainChar < 0;
      }
    },
    methods: {
      sendTweet: function (params) {
        api.post('statuses/update', {
          status: params.text,
          in_reply_to_status_id: params.in_reply_to_status_id
        }, (error, tweet, response)=>{
          if (error) {
            this.createNotification("ツイートの投稿に失敗しました", this.newTweet.text, null, 'fail');
            return console.log('error', error.map((e)=>e.message).join("\n"),  error);
          }
          console.log(tweet, response);
          this.createNotification("ツイートが投稿されました", this.newTweet.text, null, 'tweet');
          this.newTweet.text = '';
          this.newTweet.in_reply_to_status_id = '';
        });
      },
      clearReply: function () {
        this.newTweet.text = this.newTweet.text.replace(/@[a-zA-Z0-9_]+/g, '');
        this.newTweet.in_reply_to_status_id = '';
      },
      startStreaming: function () {
        console.info("start streaming");
        this.streaming = true;
        api.stream('user', {}, (stream)=>{
          stream.on('data', (data)=>{
            console.log(data);
            if (data.friends) {
              return;
            } else if (data.delete) {
              console.log(data.delete);
              this.deleteTweet(data.delete.status.id_str);
            } else if (data.scrub_geo) {
              // location deletion
            } else if (data.limit) {
              // limit notices
            } else if (data.status_withheld) {
              // withheld content notices (status)
            } else if (data.user_withheld) {
              // withheld content notices (user)
            } else if (data.disconnect) {
              // disconnect messages
            } else if (data.warning) {
              // stall warnings
            } else if (data.event) {
              // catch some event
            } else {
              // status
              this.tweets.push(data);
              if (this.isMentionsForYou(data)) {
                this.createNotification("あなた宛のメンションがあります", data.text, data.user.profile_image_url_https, 'tweet');
              }
            }
          });
          stream.on('favorite', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification("あなたのツイートがいいねされました", data.target_object.text, data.target.profile_image_url_https, 'favorite');
            }
          });
          stream.on('unfavorite', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification("あなたのツイートがいいね取り消しされました", data.target_object.text, data.target.profile_image_url_https, 'unfavorite');
            }
          });
          stream.on('follow', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification(data.source.name + " さんにフォローされました", data.source.description, data.source.profile_image_url_https, 'follow');
            }
          });
          stream.on('list_member_added', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification(data.source.name + " さんにリスト "+data.target_object.name+" に追加されました", data.target_object.description, data.source.profile_image_url_https, 'list_member_added');
            }
          });
        });
      },
      favoriteTweet: function (tweet) {
        console.log(tweet.id_str);
        var favorites_url = tweet.favorited ? 'favorites/destroy' : 'favorites/create';
        var favorites_id = tweet.retweeted_status ? tweet.retweeted_status.id_str : tweet.id_str
        api.post(favorites_url, {id: favorites_id}, (error, _tweet, response)=>{
          if (error) {
            this.createNotification("ツイートをいいねできませんでした", tweet.text, null, 'fail');
            return console.log('error', error.map((e)=>e.message).join("\n"),  error);
          }
          console.log(_tweet, response);
          if (tweet.favorited)
            this.createNotification("ツイートをいいね取り消ししました", tweet.text, null, 'unfavorite');
          else
            this.createNotification("ツイートをいいねしました", tweet.text, null, 'favorite');
          tweet.favorited = _tweet.favorited;
          tweet.favotite_count = _tweet.favorite_count;
        });
      },
      showImage: function(url, size) {
        this.image = {url: url, width: size.w, height: size.h};
        this.showingImage = true;
      },
      closeImage: function() {
        this.image = {};
        this.showingImage = false;
      },
      sendReply: function(tweet) {
        console.log(tweet.id_str);
        this.newTweet.text = "@" + tweet.user.screen_name + " ";
        this.newTweet.in_reply_to_status_id = tweet.id_str;
        this.$els.submit_box.focus();
      },
      hasMedias: function(tweet) {
        return 'extended_entities' in tweet && 'media' in tweet.extended_entities;
      },
      isMentionsForYou: function(tweet) {
        return tweet.text.match('@' + this.me.screen_name) && !tweet.retweeted_status;
      },
      deleteTweet: function(id_str) {
        this.tweets.forEach((t,i) => {
          if (t.id_str === id_str)
            this.tweets.splice(i, 1);
        })
      },
      createNotification: function(title, body, icon, kind) {
        let notification = {title, body, kind};
        this.notifications.push(notification);
        const removeMSecond = 6000;
        window.setTimeout(()=>{
          this.removeNotification(notification);
        }, removeMSecond);
        if (!remote.getCurrentWindow().isFocused()) {
          new Notification(title, {icon: icon, body: body});
        }
      },
      removeNotification: function(notification) {
        this.notifications.forEach((n,i) => {
          if (n === notification)
            this.notifications.splice(i, 1);
        })
      },
      contextMenuOnTweet: function(tweet) {
        this.selectedTweet = tweet;
        contextMenuForTweet.popup(remote.getCurrentWindow());
      }
    }
  });
  if(setting.tokens){
    var api = getApi(setting.tokens);
    api.get('account/verify_credentials', {}, (error, data, response)=>{
      if (error) {
        return console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
      vm.me = data;
    });
    api.get('statuses/home_timeline', {count: 200}, function(error, tweets, response){
      if (error) {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
      console.log('tweets',tweets.map((t)=>t.text).join("\n"), tweets);
      tweets.forEach((tweet)=>{
        vm.tweets.push(tweet);
      });
    });
    vm.startStreaming();
  }else{
    console.warn("did not oauth");
  }
});
<|endoftext|>


<commit_before>import {getApi} from '../common/oauth';
const remote = require('electron').remote;
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;
const clipboard = require('electron').clipboard;
const dialog = remote.dialog;
const path = require('path');
const Vue = require('vue');
require('twitter-text');
var setting = JSON.parse(localStorage["ulttwiclient"] || '{}');
console.log(setting.tokens);

function anchorHTML(text, href) {
  return ''+text+''
}

window.addEventListener('load',()=>{
  Vue.filter('expand_url', function (text, entities){
    entities.urls.forEach((url)=>{
      text = text.replace(url.url, anchorHTML(url.display_url, url.expanded_url));
    });
    if (entities.media) {
      entities.media.forEach((m)=>{
        text = text.replace(m.url, anchorHTML(m.display_url, m.expanded_url));
      });
    }
    return text;
  });
  Vue.filter('twemoji', function (text){
    return twemoji.parse(text);
  });
  Vue.filter('html_escape', function (text){
    return text.replace(/&/g, '&')
        .replace(/</g, '<')
        .replace(/>/g, '>')
        .replace(/"/g, '"')
        .replace(/'/g, ''');
  });
  Vue.component('small-tweet', {
    template: '#small-tweet',
    props: {
      tweet: Object,
      replysender: Boolean
    },
    data: function() {
      return {vm}
    }
  });
  Vue.filter('text_content', function (html){
    let dom = document.createElement('div');
    dom.innerHTML = html;
    return dom.firstChild.textContent;
  });
  // context menu on status
  var contextMenuForTweet = Menu.buildFromTemplate([
    {label: "ツイートに返信する", click: ()=>{vm.sendReply(vm.selectedTweet);}},
    {label: "ツイートをふぁぼる", click: ()=>{vm.favoriteTweet(vm.selectedTweet);}},
    {type: 'separator'},
    {label: "ツイートをブラウザで開く", click: ()=>{clipboard.writeText(vm.jumpToTwitterURL(vm.selectedTweet));}},
    {label: "ツイートのURLを取得", click: ()=>{clipboard.writeText(vm.createTwitterURL(vm.selectedTweet));}},
    {label: "ツイートのJSONを取得", click: ()=>{clipboard.writeText(JSON.stringify(vm.selectedTweet));}}
  ]);
  // context menu on image
  var contextMenuForImage = Menu.buildFromTemplate([
    {label: "名前を付けて画像を保存", click: ()=>{vm.saveImage();}}
  ]);
  var vm = new Vue({
    el: "#container",
    data: {
      newTweet: {
        text: "",
        in_reply_to_status_id: ""
      },
      selectedTweet: {},
      sendReplyDestTweet: {},
      me: {},
      streaming: false,
      showingMediaType: '',
      image: {},
      video: {},
      tweets: [],
      notifications: [],
      maxTweetLength: 140,
      borderOfLongTweet: 20,
      nowTime: new Date()
    },
    computed: {
      calculateRemainChar: function() {
        return this.maxTweetLength - twttr.txt.getTweetLength(this.newTweet.text);
      },
      isLongTweet: function() {
        return this.borderOfLongTweet > this.calculateRemainChar;
      },
      isExceededTweet: function() {
        return this.calculateRemainChar < 0;
      }
    },
    methods: {
      addTweet: function (tweet) {
        let _tweet = this.body(tweet);
        if (_tweet.in_reply_to_status_id_str) {
          api.get('statuses/show', {id: _tweet.in_reply_to_status_id_str},
            (error, in_reply_to_status, response)=>{
            if (!error){
              _tweet.in_reply_to_status = {};
              Object.assign(_tweet.in_reply_to_status, in_reply_to_status);
              this.tweets.push(tweet);
            } else {
              console.error(error);
            }
          });
        } else {
          this.tweets.push(tweet);
        }
      },
      sendTweet: function (params) {
        api.post('statuses/update', {
          status: params.text,
          in_reply_to_status_id: params.in_reply_to_status_id
        }, (error, tweet, response)=>{
          if (error) {
            this.createNotification("ツイートの投稿に失敗しました", this.newTweet.text, null, 'fail');
            return console.log('error', error.map((e)=>e.message).join("\n"),  error);
          }
          console.log(tweet, response);
          this.createNotification("ツイートが投稿されました", this.newTweet.text, null, 'tweet');
          this.newTweet.text = '';
          this.newTweet.in_reply_to_status_id = '';
          this.sendReplyDestTweet = {};
        });
      },
      clearReply: function () {
        this.newTweet.text = this.newTweet.text.replace(/@[a-zA-Z0-9_]+\s*/g, '');
        this.newTweet.in_reply_to_status_id = '';
        this.sendReplyDestTweet = {};
      },
      startStreaming: function () {
        console.info("start streaming");
        this.streaming = true;
        api.stream('user', {}, (stream)=>{
          stream.on('data', (data)=>{
            console.log(data);
            if (data.friends) {
              return;
            } else if (data.delete) {
              console.log(data.delete);
              this.deleteTweet(data.delete.status.id_str);
            } else if (data.scrub_geo) {
              // location deletion
            } else if (data.limit) {
              // limit notices
            } else if (data.status_withheld) {
              // withheld content notices (status)
            } else if (data.user_withheld) {
              // withheld content notices (user)
            } else if (data.disconnect) {
              // disconnect messages
            } else if (data.warning) {
              // stall warnings
            } else if (data.event) {
              // catch some event
            } else {
              // status
              this.addTweet(data);
              if (this.isMentionsForYou(data)) {
                this.createNotification("あなた宛のメンションがあります", data.text, data.user.profile_image_url_https, 'tweet');
              }
            }
          });
          stream.on('favorite', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification("あなたのツイートがいいねされました", data.target_object.text, data.target.profile_image_url_https, 'favorite');
            }
          });
          stream.on('unfavorite', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification("あなたのツイートがいいね取り消しされました", data.target_object.text, data.target.profile_image_url_https, 'unfavorite');
            }
          });
          stream.on('follow', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification(data.source.name + " さんにフォローされました", data.source.description, data.source.profile_image_url_https, 'follow');
            }
          });
          stream.on('list_member_added', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification(data.source.name + " さんにリスト "+data.target_object.name+" に追加されました", data.target_object.description, data.source.profile_image_url_https, 'list_member_added');
            }
          });
        });
      },
      selectTweet: function (tweet) {
        this.selectedTweet = tweet;
      },
      toggleSelectedTweet: function (tweet) {
        if (this.isSelectedTweet(tweet))
          this.selectedTweet = {}
        else
          this.selectTweet(tweet);
      },
      isSelectedTweet: function(tweet) {
        return this.selectedTweet && this.selectedTweet.id_str == tweet.id_str;
      },
      favoriteTweet: function (tweet) {
        console.log(tweet.id_str);
        var favorites_url = tweet.favorited ? 'favorites/destroy' : 'favorites/create';
        var favorites_id = tweet.retweeted_status ? tweet.retweeted_status.id_str : tweet.id_str
        api.post(favorites_url, {id: favorites_id}, (error, _tweet, response)=>{
          if (error) {
            this.createNotification("ツイートをいいねできませんでした", tweet.text, null, 'fail');
            return console.log('error', error.map((e)=>e.message).join("\n"),  error);
          }
          console.log(_tweet, response);
          if (tweet.favorited)
            this.createNotification("ツイートをいいね取り消ししました", tweet.text, null, 'unfavorite');
          else
            this.createNotification("ツイートをいいねしました", tweet.text, null, 'favorite');
          Object.assign(tweet, _tweet);
        });
      },
      showImage: function(url, size) {
        this.image = {url: url, width: size.w, height: size.h};
        this.showingMediaType = 'photo';
      },
      showVideo: function(url, mimetype) {
        this.video = {url, mimetype};
        this.showingMediaType = 'video';
      },
      showMedia: function(media) {
        if(media.type == 'photo')
          this.showImage(media.media_url, media.sizes.small);
        else {
          media.video_info.variants.some((v) => {
            if (v.content_type == 'video/mp4') {
              this.showVideo(v.url, v.content_type);
              return true;
            }
          });
        }
      },
      closeMedia: function() {
        this.image = {};
        this.video = {};
        this.showingMediaType = '';
      },
      sendReply: function(tweet) {
        console.log(tweet.id_str);
        this.sendReplyDestTweet = tweet;
        this.newTweet.text = "@" + tweet.user.screen_name + " ";
        this.newTweet.in_reply_to_status_id = tweet.id_str;
        this.$els.submit_box.focus();
      },
      hasMedias: function(tweet) {
        return 'extended_entities' in tweet && 'media' in tweet.extended_entities;
      },
      isMentionsForYou: function(tweet) {
        return tweet.text.match('@' + this.me.screen_name) && !tweet.retweeted_status;
      },
      body: function (tweet){
        return tweet.retweeted_status || tweet;
      },
      createTwitterURL: function (tweet){
        return 'https://twitter.com/' + tweet.user.screen_name + '/status/' + tweet.id_str;
      },
      jumpToTwitterURL: function (tweet){
        require('shell').openExternal(this.createTwitterURL(tweet));
      },
      deleteTweet: function(id_str) {
        this.tweets.forEach((t,i) => {
          if (t.id_str === id_str)
            this.tweets.splice(i, 1);
        })
      },
      createNotification: function(title, body, icon, kind) {
        let notification = {title, body, kind};
        this.notifications.push(notification);
        const removeMSecond = 6000;
        window.setTimeout(()=>{
          this.removeNotification(notification);
        }, removeMSecond);
        if (!remote.getCurrentWindow().isFocused()) {
          new Notification(title, {icon: icon, body: body});
        }
      },
      removeNotification: function(notification) {
        this.notifications.forEach((n,i) => {
          if (n === notification)
            this.notifications.splice(i, 1);
        })
      },
      contextMenuOnTweet: function(tweet) {
        this.selectTweet(tweet);
        contextMenuForTweet.popup(remote.getCurrentWindow());
      },
      contextMenuOnImage: function() {
        contextMenuForImage.popup(remote.getCurrentWindow());
      },
      saveImage: function() {
        const basename = path.basename(this.image.url);
        const defaultPath = path.dirname(require.main.filename) + basename;
        dialog.showSaveDialog({defaultPath}, (filename)=>{
          require('request')(this.image.url).pipe(require('fs').createWriteStream(filename)).on('close', ()=>{
            this.createNotification('画像の保存に成功しました', basename, null, 'success');
          });
        });
      },
      detectKeyDown: function(event) {
        if (event.ctrlKey && event.keyIdentifier == "Enter" && document.activeElement == this.$els.submit_box) {
          this.sendTweet(this.newTweet);
        }
      },
      convertTime: function(time) {
        let date = new Date(time);
        let diff = Math.floor((this.nowTime.getTime() - date.getTime()) / 1000);
        if (diff < 60) {
          return diff + "秒";
        } else if (diff < 3600) {
          return Math.floor(diff / 60) + "分";
        } else if (diff < 86400) {
          return Math.floor(diff / 3600) + "時間";
        } else if (diff < 31536000) {
          return Math.floor(diff / 86400) + "日";
        } else {
          return Math.floor(diff / 31536000) + "年";
        }
      }
    }
  });
  if(setting.tokens){
    var api = getApi(setting.tokens);
    api.get('account/verify_credentials', {}, (error, data, response)=>{
      if (error) {
        return console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
      vm.me = data;
    });
    api.get('statuses/home_timeline', {count: 200}, function(error, tweets, response){
      if (error) {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
      console.log('tweets',tweets.map((t)=>t.text).join("\n"), tweets);
      tweets.forEach((tweet)=>{
        vm.addTweet(tweet);
      });
    });
    vm.startStreaming();
    window.setInterval(()=>{
      vm.nowTime = new Date();
    }, 1000);
  }else{
    console.warn("did not oauth");
  }
});
<commit_msg>誰からふぁぼ|RTされたか分かるようにした<commit_after>import {getApi} from '../common/oauth';
const remote = require('electron').remote;
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;
const clipboard = require('electron').clipboard;
const dialog = remote.dialog;
const path = require('path');
const Vue = require('vue');
require('twitter-text');
var setting = JSON.parse(localStorage["ulttwiclient"] || '{}');
console.log(setting.tokens);

function anchorHTML(text, href) {
  return ''+text+''
}

window.addEventListener('load',()=>{
  Vue.filter('expand_url', function (text, entities){
    entities.urls.forEach((url)=>{
      text = text.replace(url.url, anchorHTML(url.display_url, url.expanded_url));
    });
    if (entities.media) {
      entities.media.forEach((m)=>{
        text = text.replace(m.url, anchorHTML(m.display_url, m.expanded_url));
      });
    }
    return text;
  });
  Vue.filter('twemoji', function (text){
    return twemoji.parse(text);
  });
  Vue.filter('html_escape', function (text){
    return text.replace(/&/g, '&')
        .replace(/</g, '<')
        .replace(/>/g, '>')
        .replace(/"/g, '"')
        .replace(/'/g, ''');
  });
  Vue.component('small-tweet', {
    template: '#small-tweet',
    props: {
      tweet: Object,
      replysender: Boolean
    },
    data: function() {
      return {vm}
    }
  });
  Vue.filter('text_content', function (html){
    let dom = document.createElement('div');
    dom.innerHTML = html;
    return dom.firstChild.textContent;
  });
  // context menu on status
  var contextMenuForTweet = Menu.buildFromTemplate([
    {label: "ツイートに返信する", click: ()=>{vm.sendReply(vm.selectedTweet);}},
    {label: "ツイートをふぁぼる", click: ()=>{vm.favoriteTweet(vm.selectedTweet);}},
    {type: 'separator'},
    {label: "ツイートをブラウザで開く", click: ()=>{clipboard.writeText(vm.jumpToTwitterURL(vm.selectedTweet));}},
    {label: "ツイートのURLを取得", click: ()=>{clipboard.writeText(vm.createTwitterURL(vm.selectedTweet));}},
    {label: "ツイートのJSONを取得", click: ()=>{clipboard.writeText(JSON.stringify(vm.selectedTweet));}}
  ]);
  // context menu on image
  var contextMenuForImage = Menu.buildFromTemplate([
    {label: "名前を付けて画像を保存", click: ()=>{vm.saveImage();}}
  ]);
  var vm = new Vue({
    el: "#container",
    data: {
      newTweet: {
        text: "",
        in_reply_to_status_id: ""
      },
      selectedTweet: {},
      sendReplyDestTweet: {},
      me: {},
      streaming: false,
      showingMediaType: '',
      image: {},
      video: {},
      tweets: [],
      notifications: [],
      maxTweetLength: 140,
      borderOfLongTweet: 20,
      nowTime: new Date()
    },
    computed: {
      calculateRemainChar: function() {
        return this.maxTweetLength - twttr.txt.getTweetLength(this.newTweet.text);
      },
      isLongTweet: function() {
        return this.borderOfLongTweet > this.calculateRemainChar;
      },
      isExceededTweet: function() {
        return this.calculateRemainChar < 0;
      }
    },
    methods: {
      addTweet: function (tweet) {
        let _tweet = this.body(tweet);
        if (_tweet.in_reply_to_status_id_str) {
          api.get('statuses/show', {id: _tweet.in_reply_to_status_id_str},
            (error, in_reply_to_status, response)=>{
            if (!error){
              _tweet.in_reply_to_status = {};
              Object.assign(_tweet.in_reply_to_status, in_reply_to_status);
              this.tweets.push(tweet);
            } else {
              console.error(error);
            }
          });
        } else {
          this.tweets.push(tweet);
        }
      },
      sendTweet: function (params) {
        api.post('statuses/update', {
          status: params.text,
          in_reply_to_status_id: params.in_reply_to_status_id
        }, (error, tweet, response)=>{
          if (error) {
            this.createNotification("ツイートの投稿に失敗しました", this.newTweet.text, null, 'fail');
            return console.log('error', error.map((e)=>e.message).join("\n"),  error);
          }
          console.log(tweet, response);
          this.createNotification("ツイートが投稿されました", this.newTweet.text, null, 'tweet');
          this.newTweet.text = '';
          this.newTweet.in_reply_to_status_id = '';
          this.sendReplyDestTweet = {};
        });
      },
      clearReply: function () {
        this.newTweet.text = this.newTweet.text.replace(/@[a-zA-Z0-9_]+\s*/g, '');
        this.newTweet.in_reply_to_status_id = '';
        this.sendReplyDestTweet = {};
      },
      startStreaming: function () {
        console.info("start streaming");
        this.streaming = true;
        api.stream('user', {}, (stream)=>{
          stream.on('data', (data)=>{
            console.log(data);
            if (data.friends) {
              return;
            } else if (data.delete) {
              console.log(data.delete);
              this.deleteTweet(data.delete.status.id_str);
            } else if (data.scrub_geo) {
              // location deletion
            } else if (data.limit) {
              // limit notices
            } else if (data.status_withheld) {
              // withheld content notices (status)
            } else if (data.user_withheld) {
              // withheld content notices (user)
            } else if (data.disconnect) {
              // disconnect messages
            } else if (data.warning) {
              // stall warnings
            } else if (data.event) {
              // catch some event
            } else {
              // status
              this.addTweet(data);
              if (this.isMentionsForYou(data)) {
                this.createNotification(data.user.name+" さんからあなた宛のメンションがあります", data.text, data.user.profile_image_url_https, 'tweet');
              }
            }
          });
          stream.on('favorite', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification("あなたのツイートが "+data.source.name+" にいいねされました", data.target_object.text, data.target.profile_image_url_https, 'favorite');
            }
          });
          stream.on('unfavorite', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification("あなたのツイートが "+data.source.name+" にいいね取り消しされました", data.target_object.text, data.target.profile_image_url_https, 'unfavorite');
            }
          });
          stream.on('follow', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification(data.source.name + " さんにフォローされました", data.source.description, data.source.profile_image_url_https, 'follow');
            }
          });
          stream.on('list_member_added', (data)=>{
            if (data.target.screen_name === this.me.screen_name) {
              this.createNotification(data.source.name + " さんにリスト "+data.target_object.name+" に追加されました", data.target_object.description, data.source.profile_image_url_https, 'list_member_added');
            }
          });
        });
      },
      selectTweet: function (tweet) {
        this.selectedTweet = tweet;
      },
      toggleSelectedTweet: function (tweet) {
        if (this.isSelectedTweet(tweet))
          this.selectedTweet = {}
        else
          this.selectTweet(tweet);
      },
      isSelectedTweet: function(tweet) {
        return this.selectedTweet && this.selectedTweet.id_str == tweet.id_str;
      },
      favoriteTweet: function (tweet) {
        console.log(tweet.id_str);
        var favorites_url = tweet.favorited ? 'favorites/destroy' : 'favorites/create';
        var favorites_id = tweet.retweeted_status ? tweet.retweeted_status.id_str : tweet.id_str
        api.post(favorites_url, {id: favorites_id}, (error, _tweet, response)=>{
          if (error) {
            this.createNotification("ツイートをいいねできませんでした", tweet.text, null, 'fail');
            return console.log('error', error.map((e)=>e.message).join("\n"),  error);
          }
          console.log(_tweet, response);
          if (tweet.favorited)
            this.createNotification("ツイートをいいね取り消ししました", tweet.text, null, 'unfavorite');
          else
            this.createNotification("ツイートをいいねしました", tweet.text, null, 'favorite');
          Object.assign(tweet, _tweet);
        });
      },
      showImage: function(url, size) {
        this.image = {url: url, width: size.w, height: size.h};
        this.showingMediaType = 'photo';
      },
      showVideo: function(url, mimetype) {
        this.video = {url, mimetype};
        this.showingMediaType = 'video';
      },
      showMedia: function(media) {
        if(media.type == 'photo')
          this.showImage(media.media_url, media.sizes.small);
        else {
          media.video_info.variants.some((v) => {
            if (v.content_type == 'video/mp4') {
              this.showVideo(v.url, v.content_type);
              return true;
            }
          });
        }
      },
      closeMedia: function() {
        this.image = {};
        this.video = {};
        this.showingMediaType = '';
      },
      sendReply: function(tweet) {
        console.log(tweet.id_str);
        this.sendReplyDestTweet = tweet;
        this.newTweet.text = "@" + tweet.user.screen_name + " ";
        this.newTweet.in_reply_to_status_id = tweet.id_str;
        this.$els.submit_box.focus();
      },
      hasMedias: function(tweet) {
        return 'extended_entities' in tweet && 'media' in tweet.extended_entities;
      },
      isMentionsForYou: function(tweet) {
        return tweet.text.match('@' + this.me.screen_name) && !tweet.retweeted_status;
      },
      body: function (tweet){
        return tweet.retweeted_status || tweet;
      },
      createTwitterURL: function (tweet){
        return 'https://twitter.com/' + tweet.user.screen_name + '/status/' + tweet.id_str;
      },
      jumpToTwitterURL: function (tweet){
        require('shell').openExternal(this.createTwitterURL(tweet));
      },
      deleteTweet: function(id_str) {
        this.tweets.forEach((t,i) => {
          if (t.id_str === id_str)
            this.tweets.splice(i, 1);
        })
      },
      createNotification: function(title, body, icon, kind) {
        let notification = {title, body, kind};
        this.notifications.push(notification);
        const removeMSecond = 6000;
        window.setTimeout(()=>{
          this.removeNotification(notification);
        }, removeMSecond);
        if (!remote.getCurrentWindow().isFocused()) {
          new Notification(title, {icon: icon, body: body});
        }
      },
      removeNotification: function(notification) {
        this.notifications.forEach((n,i) => {
          if (n === notification)
            this.notifications.splice(i, 1);
        })
      },
      contextMenuOnTweet: function(tweet) {
        this.selectTweet(tweet);
        contextMenuForTweet.popup(remote.getCurrentWindow());
      },
      contextMenuOnImage: function() {
        contextMenuForImage.popup(remote.getCurrentWindow());
      },
      saveImage: function() {
        const basename = path.basename(this.image.url);
        const defaultPath = path.dirname(require.main.filename) + basename;
        dialog.showSaveDialog({defaultPath}, (filename)=>{
          require('request')(this.image.url).pipe(require('fs').createWriteStream(filename)).on('close', ()=>{
            this.createNotification('画像の保存に成功しました', basename, null, 'success');
          });
        });
      },
      detectKeyDown: function(event) {
        if (event.ctrlKey && event.keyIdentifier == "Enter" && document.activeElement == this.$els.submit_box) {
          this.sendTweet(this.newTweet);
        }
      },
      convertTime: function(time) {
        let date = new Date(time);
        let diff = Math.floor((this.nowTime.getTime() - date.getTime()) / 1000);
        if (diff < 60) {
          return diff + "秒";
        } else if (diff < 3600) {
          return Math.floor(diff / 60) + "分";
        } else if (diff < 86400) {
          return Math.floor(diff / 3600) + "時間";
        } else if (diff < 31536000) {
          return Math.floor(diff / 86400) + "日";
        } else {
          return Math.floor(diff / 31536000) + "年";
        }
      }
    }
  });
  if(setting.tokens){
    var api = getApi(setting.tokens);
    api.get('account/verify_credentials', {}, (error, data, response)=>{
      if (error) {
        return console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
      vm.me = data;
    });
    api.get('statuses/home_timeline', {count: 200}, function(error, tweets, response){
      if (error) {
        console.log('error', error.map((e)=>e.message).join("\n"),  error);
      }
      console.log('tweets',tweets.map((t)=>t.text).join("\n"), tweets);
      tweets.forEach((tweet)=>{
        vm.addTweet(tweet);
      });
    });
    vm.startStreaming();
    window.setInterval(()=>{
      vm.nowTime = new Date();
    }, 1000);
  }else{
    console.warn("did not oauth");
  }
});
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';

import './Emoji.css';

class Emoji extends Component {
  static propTypes = {
    emoji: PropTypes.string.isRequired,
    description: PropTypes.string.isRequired
  };

  constructor(props) {
    super(props);
    this.state = {
      copying: false
    };
  }

  onClick(e) {
    this.refs.input.select();
    document.execCommand('copy');

    this.setState({ copying: true });
    setTimeout(() => this.setState({ copying: false }), 1000);
  }

  render() {
    const { emoji, description } = this.props;
    return (
      <button className="Emoji" onClick={this.onClick.bind(this)}>
        {this.state.copying && ✂}
        {emoji}

{description}

        <input className="hidden" type="text" readOnly ref="input" value={emoji} />
      </button>
    );
  }
}

export default Emoji;
<commit_msg>fix(copy-emoji) for iphone<commit_after>import React, { Component, PropTypes } from 'react';

import './Emoji.css';

class Emoji extends Component {
  static propTypes = {
    emoji: PropTypes.string.isRequired,
    description: PropTypes.string.isRequired
  };

  constructor(props) {
    super(props);
    this.state = {
      copying: false
    };
  }

  onClick(e) {
    const input = this.refs.input;

    input.select();
    input.selectionStart=0;
    input.selectionEnd = input.value.length;

    document.execCommand('copy');

    this.setState({ copying: true });
    setTimeout(() => this.setState({ copying: false }), 1000);
  }

  render() {
    const { emoji, description } = this.props;
    return (
      <button className="Emoji" onClick={this.onClick.bind(this)}>
        {this.state.copying && ✂}
        {emoji}

{description}

        <input className="hidden" type="text" readOnly ref="input" value={emoji} />
      </button>
    );
  }
}

export default Emoji;
<|endoftext|>


<commit_before>'use strict'
/**
 * index.js
 * Entry point for app execution
 ******************************/
// Electron

const { app, dialog } = require('electron')
// declare global constants
// MasterPass credentials global
global.creds = {}
// User settings global
global.settings = {}
// Paths global (only resolved at runtime)
global.paths = {
  mdb: `${app.getPath('userData')}/mdb`,
  userData: app.getPath('userData'),
  home: app.getPath('home'),
  documents: app.getPath('documents')
}

const logger = require('./script/logger')
const { checkUpdate } = require('./script/utils')
// Core
const Db = require('./core/Db')
// Windows
const crypter = require('./src/crypter')
const masterPassPrompt = require('./src/masterPassPrompt')
const setup = require('./src/setup')
const settings = require('./src/settings')
const { ERRORS } = require('./config')
// adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')()

// change exec path
logger.info(`Crypter v${app.getVersion()}`)
logger.info(`AppPath: ${app.getAppPath()}`)
logger.info(`UseData Path: ${app.getPath('userData')}`)
process.chdir(app.getAppPath())
logger.info(`Changed cwd to: ${process.cwd()}`)
logger.info(`Electron v${process.versions.electron}`)
logger.info(`Electron node v${process.versions.node}`)

/**
 * Promisification of initialisation
 **/

const init = function () {
  return new Promise(function (resolve, reject) {
    // initialise mdb
    global.mdb = new Db(global.paths.mdb, function (mdb) {
      // Get the credentials serialized object from mdb
      resolve(mdb.get('creds'))
    })
  })
}

const initMain = function () {
  logger.verbose(`PROMISE: Main initialisation`)
  return new Promise(function (resolve, reject) {
    // restore the creds object globally
    resolve(global.mdb.restoreGlobalObj('creds'))
  })
}

/**
 * Event handlers
 **/

// Main event handler
app.on('ready', function () {
  // Check for updates
  checkUpdate()
    .catch((err) => { logger.warn(err) })
  // Check synchronously whether paths exist
  init()
    .then((mainRun) => {
      logger.info(`Init done.`)
      // If the credentials not find in mdb, run setup
      // otherwise run main
      if (mainRun) {
        // Run main
        logger.info(`Main run. Creating CrypterWindow...`)

        // Initialise (open mdb and get creds)
        initMain()
          .then(() => {
            // Obtain MasterPass, derive MasterPassKey and set globally
            return masterPassPromptWindow()
          })
          .then(() => {
            // Create the Crypter window and open it
            return crypterWindow()
          })
          .then(() => {
            // Quit app after crypterWindow is closed
            app.quit()
          })
          .catch(function (error) {
            if (error) {
              // Catch any fatal errors and exit
              logger.error(`PROMISE ERR: ${error.stack}`)
              dialog.showErrorBox(ERRORS.PROMISE, error.message)
            }
            app.quit()
          })
      } else {
        // Run Setup
        logger.info('Setup run. Creating Setup wizard...')

        setupWindow()
          .then(() => {
            logger.info('MAIN Setup successfully completed. quitting...')
            // setup successfully completed
            app.quit()
          })
          .catch(function (error) {
            logger.error(`PROMISE ERR: ${error.stack}`)
            // Display error to user
            dialog.showErrorBox(ERRORS.PROMISE, error.message)
            app.quit()
          })
      }
    })
    .catch(function (error) {
      logger.error(`PROMISE ERR: ${error.stack}`)
      // Display error to user
      dialog.showErrorBox(ERRORS.PROMISE, error.message)
      app.quit()
    })
})

/**
 * Electron events
 **/
let settingsWindowNotOpen = true

app.on('window-all-closed', () => {
  logger.verbose('APP: window-all-closed event emitted')
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
})

app.on('quit', () => {
  logger.info('APP: quit event emitted')
  global.mdb.close()
    .catch((err) => {
      console.error(err)
      throw err
    })
})

app.on('will-quit', (event) => {
  // will exit program once exit procedures have been run
  logger.info(`APP.ON('will-quit'): will-quit event emitted`)
  global.mdb.close()
    .catch((err) => {
      console.error(err)
      throw err
    })
})

/**
 * Custom events
 **/

app.on('app:quit', () => {
  logger.verbose('APP: app:quit event emitted')
  app.quit()
})

app.on('app:open-settings', () => {
  logger.verbose('APP: app:open-settings event emitted')
  // Check if not already opened
  if (settingsWindowNotOpen) {
    settingsWindowNotOpen = false
    settingsWindow()
      .then(() => {
        logger.verbose('APP: closed settingsWindow')
        // Closed so not open anymore
        settingsWindowNotOpen = true
      })
  }
})

app.on('app:check-update', () => {
  logger.verbose('APP: app:check-updates event emitted')
  // Check for updates
  checkUpdate()
    .then((updateAvailable) => {
      if (!updateAvailable) {
        dialog.showMessageBox({
          type: 'info',
          message: 'No update available.',
          detail: `You have the latest version Crypter ${app.getVersion()} :)`
        })
      }
    })
    .catch((err) => {
      dialog.showErrorBox('Failed to check for update',
        `An error occured while checking for update:\n ${err.message}`)
    })
})

app.on('app:relaunch', () => {
  logger.verbose('APP: app:relaunch event emitted')
  // Relaunch Crypter
  app.relaunch()
  // Exit successfully
  app.quit(0)
  // app.exit(0)
})

/**
 * Promisification of windows
 **/

// Creates the crypter window
let crypterWindow = function () {
  return new Promise(function (resolve, reject) {
    crypter.window(global, function () {
      resolve()
    })
  })
}

// Creates the setup window
let setupWindow = function () {
  return new Promise(function (resolve, reject) {
    setup.window(global, function (err) {
      if (err) {
        reject(err)
      } else {
        resolve()
      }
    })
  })
}

// Creates the MasterPassPrompt window
let masterPassPromptWindow = function () {
  return new Promise(function (resolve, reject) {
    masterPassPrompt.window(global, function (err, gotMP) {
      if (err || !gotMP) reject(err)
      resolve()
    })
  })
}

// Creates the settings window
let settingsWindow = function () {
  return new Promise(function (resolve, reject) {
    settings.window(global, function () {
      resolve()
    })
  })
}<commit_msg>finish implementing and close #18<commit_after>'use strict'
/**
 * index.js
 * Entry point for app execution
 ******************************/
// Electron

const { app, dialog } = require('electron')
// declare global constants
// MasterPass credentials global
global.creds = {}
// User settings global
global.settings = {}
// Paths global (only resolved at runtime)
global.paths = {
  mdb: `${app.getPath('userData')}/mdb`,
  userData: app.getPath('userData'),
  home: app.getPath('home'),
  documents: app.getPath('documents')
}

const logger = require('./script/logger')
const { checkUpdate } = require('./script/utils')
// Core
const Db = require('./core/Db')
// Windows
const crypter = require('./src/crypter')
const masterPassPrompt = require('./src/masterPassPrompt')
const setup = require('./src/setup')
const settings = require('./src/settings')
const { ERRORS } = require('./config')
// adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')()

// change exec path
logger.info(`Crypter v${app.getVersion()}`)
logger.info(`AppPath: ${app.getAppPath()}`)
logger.info(`UseData Path: ${app.getPath('userData')}`)
process.chdir(app.getAppPath())
logger.info(`Changed cwd to: ${process.cwd()}`)
logger.info(`Electron v${process.versions.electron}`)
logger.info(`Electron node v${process.versions.node}`)

/**
 * Promisification of initialisation
 **/

const init = function () {
  return new Promise(function (resolve, reject) {
    // initialise mdb
    global.mdb = new Db(global.paths.mdb, function (mdb) {
      // Get the credentials serialized object from mdb
      resolve(mdb.get('creds'))
    })
  })
}

const initMain = function () {
  logger.verbose(`PROMISE: Main initialisation`)
  return new Promise(function (resolve, reject) {
    // restore the creds object globally
    resolve(global.mdb.restoreGlobalObj('creds'))
  })
}

/**
 * Event handlers
 **/

// Main event handler
app.on('ready', function () {
  // Check for updates
  checkUpdate()
    .catch((err) => { logger.warn(err) })
  // Check synchronously whether paths exist
  init()
    .then((mainRun) => {
      logger.info(`Init done.`)
      // If the credentials not find in mdb, run setup
      // otherwise run main
      if (mainRun) {
        // Run main
        logger.info(`Main run. Creating CrypterWindow...`)

        // Initialise (open mdb and get creds)
        initMain()
          .then(() => {
            // Obtain MasterPass, derive MasterPassKey and set globally
            return masterPassPromptWindow()
          })
          .then(() => {
            // Create the Crypter window and open it
            return crypterWindow()
          })
          .then(() => {
            // Quit app after crypterWindow is closed
            app.quit()
          })
          .catch(function (error) {
            if (error) {
              // Catch any fatal errors and exit
              logger.error(`PROMISE ERR: ${error.stack}`)
              dialog.showErrorBox(ERRORS.PROMISE, error.message)
            }
            app.quit()
          })
      } else {
        // Run Setup
        logger.info('Setup run. Creating Setup wizard...')

        setupWindow()
          .then(() => {
            logger.info('MAIN Setup successfully completed. quitting...')
            // setup successfully completed
            app.quit()
          })
          .catch(function (error) {
            logger.error(`PROMISE ERR: ${error.stack}`)
            // Display error to user
            dialog.showErrorBox(ERRORS.PROMISE, error.message)
            app.quit()
          })
      }
    })
    .catch(function (error) {
      logger.error(`PROMISE ERR: ${error.stack}`)
      // Display error to user
      dialog.showErrorBox(ERRORS.PROMISE, error.message)
      app.quit()
    })
})

/**
 * Electron events
 **/
let settingsWindowNotOpen = true

app.on('window-all-closed', () => {
  logger.verbose('APP: window-all-closed event emitted')
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
})

app.on('quit', () => {
  logger.info('APP: quit event emitted')
  global.mdb.close()
    .catch((err) => {
      console.error(err)
      throw err
    })
})

app.on('will-quit', (event) => {
  // will exit program once exit procedures have been run
  logger.info(`APP.ON('will-quit'): will-quit event emitted`)
  global.mdb.close()
    .catch((err) => {
      console.error(err)
      throw err
    })
})

/**
 * Custom events
 **/

app.on('app:quit', () => {
  logger.verbose('APP: app:quit event emitted')
  app.quit()
})

app.on('app:open-settings', () => {
  logger.verbose('APP: app:open-settings event emitted')
  // Check if not already opened
  if (settingsWindowNotOpen) {
    settingsWindowNotOpen = false
    settingsWindow()
      .then(() => {
        logger.verbose('APP: closed settingsWindow')
        // Closed so not open anymore
        settingsWindowNotOpen = true
      })
  }
})

app.on('app:check-update', () => {
  logger.verbose('APP: app:check-updates event emitted')
  // Check for updates
  checkUpdate()
    .then((updateAvailable) => {
      if (!updateAvailable) {
        dialog.showMessageBox({
          type: 'info',
          message: 'No update available.',
          detail: `You have the latest version Crypter ${app.getVersion()} :)`
        })
      }
    })
    .catch((err) => {
      logger.warn(err)
      dialog.showErrorBox('Failed to check for update',
        `An error occured while checking for update:\n ${err.message}`)
    })
})

app.on('app:relaunch', () => {
  logger.verbose('APP: app:relaunch event emitted')
  // Relaunch Crypter
  app.relaunch()
  // Exit successfully
  app.quit(0)
  // app.exit(0)
})

/**
 * Promisification of windows
 **/

// Creates the crypter window
let crypterWindow = function () {
  return new Promise(function (resolve, reject) {
    crypter.window(global, function () {
      resolve()
    })
  })
}

// Creates the setup window
let setupWindow = function () {
  return new Promise(function (resolve, reject) {
    setup.window(global, function (err) {
      if (err) {
        reject(err)
      } else {
        resolve()
      }
    })
  })
}

// Creates the MasterPassPrompt window
let masterPassPromptWindow = function () {
  return new Promise(function (resolve, reject) {
    masterPassPrompt.window(global, function (err, gotMP) {
      if (err || !gotMP) reject(err)
      resolve()
    })
  })
}

// Creates the settings window
let settingsWindow = function () {
  return new Promise(function (resolve, reject) {
    settings.window(global, function () {
      resolve()
    })
  })
}<|endoftext|>


<commit_before>/*
 * Copyright (c) 1997-2013, SalesLogix, NA., LLC. All rights reserved.
 */
define('Mobile/SalesLogix/Environment', [
    'dojo/_base/lang',
    'dojo/_base/window',
    'dojo/_base/array',
    'dojo/has',
    'dojo/string',
    'dojo/dom-construct',
    'dojo/_base/sniff'
], function(
    lang,
    win,
    array,
    has,
    string,
    domConstruct
) {
    return lang.setObject('Mobile.SalesLogix.Environment', {
        // todo: open a new browser window for these when on a mobile device?
        // on a mobile device, launching an external handler can impact a view transition, and cause issues, which the timeout takes care of.
        // not the best way, perhaps a post-transition callback should be used for launching these? check transitioning, then queue if needed?
        initiateCall: function(number) {
            setTimeout(function() {
                window.location.href = string.substitute("tel:${0}", [number]);
            }, 500);
        },
        initiateEmail: function(email, subject, body) {
            setTimeout(function() {
                var mailtoUri = (subject)
                    ? string.substitute("mailto:${0}?subject=${1}&body=${2}", [email, subject, body || ''])
                    : string.substitute("mailto:${0}", [email]);
                window.location.href = mailtoUri;
            }, 1000); // 1 sec delay for iPad iOS5 to actually save nav state to local storage
        },
        showMapForAddress: function(address) {
            setTimeout(function() {
                var eventFire = function(node, eventType) {
                    if (node.fireEvent) { // for IE
                        node.fireEvent('on' + eventType);
                        node[eventType]();
                    } else {
                        var event = document.createEvent('MouseEvents');
                        event.initMouseEvent(eventType, true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
                        node.dispatchEvent(event);

                        // FF 3.6-4 do not follow untrusted events, fixed in FF5+
                        // https://bugzilla.mozilla.org/show_bug.cgi?id=666604
                        if (has('ff') < 5) {
                            window.open(node.href);
                        }
                    }
                };

                var hiddenLink = domConstruct.create('a', {
                    href: string.substitute("http://maps.google.com/maps?q=${0}", [address]),
                    target: '_blank'
                }, win.body(), 'last');

                eventFire(hiddenLink, 'click');

                domConstruct.destroy(hiddenLink);

            }, 50);
        },
        attachmentViewsToRefresh: [
            'myattachment_list',
            'attachment_list',
            'attachment_related',
            'account_attachment_related',
            'contact_attachment_related',
            'lead_attachment_related',
            'opportunity_attachment_related',
            'ticket_attachment_related',
            'activity_attachment_related',
            'history_attachment_related'
        ],
        activityViewsToRefresh: [
            'myactivity_list',
            'activity_list',
            'activity_detail',
            'activity_related',
            'ticketactivity_related',
            'history_related',
            'history_list'
        ],
        detailViewsToRefreshOnUpdate: [
            'account_detail',
            'contact_detail',
            'opportunity_detail',
            'lead_detail',
            'ticket_detail'
        ],
        refreshStaleDetailViews: function() {
            // List of detail views that will need refreshed when a note is added or an activity is completed (possibly others??).
            // Otherwise the etag will change and the server will give a 412: Preconditioned failed when we attempt to edit/save.
            var views = Mobile.SalesLogix.Environment.detailViewsToRefreshOnUpdate || [];
            Mobile.SalesLogix.Environment.refreshViews(views);
        },
        refreshActivityLists: function() {
            var views = Mobile.SalesLogix.Environment.activityViewsToRefresh || [];
            Mobile.SalesLogix.Environment.refreshViews(views);
        },
        refreshAttachmentViews: function() {
            var views = Mobile.SalesLogix.Environment.attachmentViewsToRefresh || [];
            Mobile.SalesLogix.Environment.refreshViews(views);
        },
        refreshViews: function(views) {
            if (views && views.length > 0) {
                array.forEach(views, function(view_id) {
                    var view = App.getView(view_id);
                    if (view) {
                        view.refreshRequired = true;
                    }
                });
            }
        }
    });
});

<commit_msg>Using dojo/on to emit the click event. Removed setTimeout preventing Windows 8 phone from opening the link.<commit_after>/*
 * Copyright (c) 1997-2013, SalesLogix, NA., LLC. All rights reserved.
 */
define('Mobile/SalesLogix/Environment', [
    'dojo/_base/lang',
    'dojo/_base/window',
    'dojo/_base/array',
    'dojo/has',
    'dojo/on',
    'dojo/string',
    'dojo/dom-construct',
    'dojo/_base/sniff'
], function(
    lang,
    win,
    array,
    has,
    on,
    string,
    domConstruct
) {
    return lang.setObject('Mobile.SalesLogix.Environment', {
        // todo: open a new browser window for these when on a mobile device?
        // on a mobile device, launching an external handler can impact a view transition, and cause issues, which the timeout takes care of.
        // not the best way, perhaps a post-transition callback should be used for launching these? check transitioning, then queue if needed?
        initiateCall: function(number) {
            setTimeout(function() {
                window.location.href = string.substitute("tel:${0}", [number]);
            }, 500);
        },
        initiateEmail: function(email, subject, body) {
            setTimeout(function() {
                var mailtoUri = (subject)
                    ? string.substitute("mailto:${0}?subject=${1}&body=${2}", [email, subject, body || ''])
                    : string.substitute("mailto:${0}", [email]);
                window.location.href = mailtoUri;
            }, 1000); // 1 sec delay for iPad iOS5 to actually save nav state to local storage
        },
        showMapForAddress: function(address) {
            var hiddenLink = domConstruct.create('a', {
                href: string.substitute("http://maps.google.com/maps?q=${0}", [address]),
                target: '_blank'
            }, win.body(), 'last');

            on.emit(hiddenLink, 'click', {
                bubbles: true,
                cancelable: true
            });

            domConstruct.destroy(hiddenLink);
        },
        attachmentViewsToRefresh: [
            'myattachment_list',
            'attachment_list',
            'attachment_related',
            'account_attachment_related',
            'contact_attachment_related',
            'lead_attachment_related',
            'opportunity_attachment_related',
            'ticket_attachment_related',
            'activity_attachment_related',
            'history_attachment_related'
        ],
        activityViewsToRefresh: [
            'myactivity_list',
            'activity_list',
            'activity_detail',
            'activity_related',
            'ticketactivity_related',
            'history_related',
            'history_list'
        ],
        detailViewsToRefreshOnUpdate: [
            'account_detail',
            'contact_detail',
            'opportunity_detail',
            'lead_detail',
            'ticket_detail'
        ],
        refreshStaleDetailViews: function() {
            // List of detail views that will need refreshed when a note is added or an activity is completed (possibly others??).
            // Otherwise the etag will change and the server will give a 412: Preconditioned failed when we attempt to edit/save.
            var views = Mobile.SalesLogix.Environment.detailViewsToRefreshOnUpdate || [];
            Mobile.SalesLogix.Environment.refreshViews(views);
        },
        refreshActivityLists: function() {
            var views = Mobile.SalesLogix.Environment.activityViewsToRefresh || [];
            Mobile.SalesLogix.Environment.refreshViews(views);
        },
        refreshAttachmentViews: function() {
            var views = Mobile.SalesLogix.Environment.attachmentViewsToRefresh || [];
            Mobile.SalesLogix.Environment.refreshViews(views);
        },
        refreshViews: function(views) {
            if (views && views.length > 0) {
                array.forEach(views, function(view_id) {
                    var view = App.getView(view_id);
                    if (view) {
                        view.refreshRequired = true;
                    }
                });
            }
        }
    });
});

<|endoftext|>


<commit_before>// @flow
/* eslint no-console: 0 */

import 'babel-polyfill';
import moment from 'moment';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import configureStore from 'app/utils/configureStore';
import renderApp from './render';

moment.locale('nb-NO');

global.log = function log(self = this) {
  console.log(self);
  return this;
};

const preloadedState = window.__PRELOADED_STATE__;
delete window.__PRELOADED_STATE__;

const store = configureStore(preloadedState);
const history = syncHistoryWithStore(browserHistory, store);

store.dispatch({ type: 'REHYDRATED' });

renderApp(store, history);

if (module.hot) {
  module.hot.accept('./render', () => {
    renderApp(store, history);
  });
}
<commit_msg>Fix autologin when not using SSR (#504)<commit_after>// @flow
/* eslint no-console: 0 */

import 'babel-polyfill';
import moment from 'moment';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { isEmpty } from 'lodash';
import configureStore from 'app/utils/configureStore';
import renderApp from './render';
import { loginAutomaticallyIfPossible } from 'app/actions/UserActions';

moment.locale('nb-NO');

global.log = function log(self = this) {
  console.log(self);
  return this;
};

const preloadedState = window.__PRELOADED_STATE__;
delete window.__PRELOADED_STATE__;

const store = configureStore(preloadedState);

if (isEmpty(preloadedState)) {
  store.dispatch(loginAutomaticallyIfPossible());
}

const history = syncHistoryWithStore(browserHistory, store);

store.dispatch({ type: 'REHYDRATED' });

renderApp(store, history);

if (module.hot) {
  module.hot.accept('./render', () => {
    renderApp(store, history);
  });
}
<|endoftext|>


<commit_before>import options from './options'
import { getCurrentManipulation } from './manipulation'

const nodes = new vis.DataSet([])
const edges = new vis.DataSet([])

const select   = $('select')
const resetBtn = $('#reset-btn')
const testStr  = $('input[name="testStr"]')

const alphabetPromptText = 'Please enter alphabet string: '

let currentVisualizer = 'PDA'

select.material_select()
select.change(onVisualizerChange)
resetBtn.click(e => start(prompt(alphabetPromptText)))
testStr.keypress(e => clickRunBtn(e.which))

start('01')

function start(alphabetStr) {
    if (!alphabetStr)
        return

    const alphabet = [ ...new Set(alphabetStr.split('')) ]

    $('.opt').hide()

    $('#title').text(currentVisualizer)

    nodes.clear()
    edges.clear()
    new vis.Network(document.getElementById('app'), { nodes, edges},
                    options(getCurrentManipulation(
                        currentVisualizer, alphabet, { nodes, edges })))
}

function onVisualizerChange() {
    const visSelection = select.val()
    if (currentVisualizer !== visSelection) {
        let alphabetStr = prompt(alphabetPromptText)

        if (!alphabetStr) {
            select.val(currentVisualizer)
            return
        }

        currentVisualizer = visSelection
        start(alphabetStr)
    }
}

function clickRunBtn(key) {
    if (key === 13)
        $('#run-btn').click()
}<commit_msg>Reset current visualizer to dfa<commit_after>import options from './options'
import { getCurrentManipulation } from './manipulation'

const nodes = new vis.DataSet([])
const edges = new vis.DataSet([])

const select   = $('select')
const resetBtn = $('#reset-btn')
const testStr  = $('input[name="testStr"]')

const alphabetPromptText = 'Please enter alphabet string: '

let currentVisualizer = 'DFA'

select.material_select()
select.change(onVisualizerChange)
resetBtn.click(e => start(prompt(alphabetPromptText)))
testStr.keypress(e => clickRunBtn(e.which))

start('01')

function start(alphabetStr) {
    if (!alphabetStr)
        return

    const alphabet = [ ...new Set(alphabetStr.split('')) ]

    $('.opt').hide()

    $('#title').text(currentVisualizer)

    nodes.clear()
    edges.clear()
    new vis.Network(document.getElementById('app'), { nodes, edges},
                    options(getCurrentManipulation(
                        currentVisualizer, alphabet, { nodes, edges })))
}

function onVisualizerChange() {
    const visSelection = select.val()
    if (currentVisualizer !== visSelection) {
        let alphabetStr = prompt(alphabetPromptText)

        if (!alphabetStr) {
            select.val(currentVisualizer)
            return
        }

        currentVisualizer = visSelection
        start(alphabetStr)
    }
}

function clickRunBtn(key) {
    if (key === 13)
        $('#run-btn').click()
}<|endoftext|>


<commit_before>'use strict';

var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var chalk = require('chalk');

var ElementGenerator = yeoman.generators.Base.extend({
    init: function () {
        this.pkg = yeoman.file.readJSON(path.join(__dirname, '../package.json'));

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

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

        console.log(chalk.magenta('\nCustom Element generator brought to you by WebComponents.org\n'));

        var prompts = [{
            name: 'elementName',
            message: 'What\'s the name of your element?',
            validate: function(input) {
                if (input === '') {
                    return 'Please fill your element name, for example: "my-element".';
                }
                else if (input.indexOf('-') === -1) {
                    return 'According to the spec, all elements must contain a dash on its name, for example: "my-element".';
                }
            }
        }, {
            name: 'elementDescription',
            message: 'What\'s the description of your element?',
            validate: function(input) {
                if (input === '') {
                    return 'Please fill your element description, for example: "My awesome Web Component using Polymer".';
                }
            }
        },
        {
            type: 'confirm',
            name: 'lifecycle',
            message: 'Do you want to include lifecycle callbacks?',
            default: false
        }];

        this.prompt(prompts, function (props) {
            this.elementName = this._.slugify(props.elementName);
            this.elementDescription = props.elementDescription;
            this.lifecycle = props.lifecycle;

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

    app: function () {
        this.copy('_bower.json', 'bower.json');
        this.copy('_package.json', 'package.json');
    },

    element: function () {
        this.mkdir('src');

        this.copy('_Gruntfile.js', 'Gruntfile.js');
        this.copy('_index.html', 'index.html');
        this.copy('_README.md', 'README.md');
        this.copy('src/_my-element.html', 'src/' + this.elementName + '.html');
    },

    dotfiles: function () {
        this.copy('bowerrc', '.bowerrc');
        this.copy('editorconfig', '.editorconfig');
        this.copy('gitignore', '.gitignore');
    }
});

module.exports = ElementGenerator;
<commit_msg>Check if element name is not contained in the blacklist - Fixes #7<commit_after>'use strict';

var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var chalk = require('chalk');

var ElementGenerator = yeoman.generators.Base.extend({
    init: function () {
        this.pkg = yeoman.file.readJSON(path.join(__dirname, '../package.json'));

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

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

        console.log(chalk.magenta('\nCustom Element generator brought to you by WebComponents.org\n'));

        var prompts = [{
            name: 'elementName',
            message: 'What\'s the name of your element?',
            validate: validateName
        }, {
            name: 'elementDescription',
            message: 'What\'s the description of your element?',
            validate: validateDescription
        },
        {
            type: 'confirm',
            name: 'lifecycle',
            message: 'Do you want to include lifecycle callbacks?',
            default: false
        }];

        this.prompt(prompts, function (props) {
            this.elementName = this._.slugify(props.elementName);
            this.elementDescription = props.elementDescription;
            this.lifecycle = props.lifecycle;

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

    app: function () {
        this.copy('_bower.json', 'bower.json');
        this.copy('_package.json', 'package.json');
    },

    element: function () {
        this.mkdir('src');

        this.copy('_Gruntfile.js', 'Gruntfile.js');
        this.copy('_index.html', 'index.html');
        this.copy('_README.md', 'README.md');
        this.copy('src/_my-element.html', 'src/' + this.elementName + '.html');
    },

    dotfiles: function () {
        this.copy('bowerrc', '.bowerrc');
        this.copy('editorconfig', '.editorconfig');
        this.copy('gitignore', '.gitignore');
    }
});

// Custom Elements Spec: Naming Rules
// http://www.w3.org/TR/custom-elements/#concepts
function validateName (input) {
    var blacklist = [
        'annotation-xml', 'color-profile', 'font-face', 'font-face-src',
        'font-face-uri', 'font-face-format', 'font-face-name', 'missing-glyph'
    ];

    if (input === '') {
        return 'Please fill your element name, for example: "my-element".';
    }
    else if (input.indexOf('-') === -1) {
        return 'According to the spec, all elements must contain a dash on its name, for example: "my-element".';
    }

    for (var i = 0; i < blacklist.length; i++) {
        if (input === blacklist[i]) {
            return 'According to the spec, this element name is not allowed.';
        }
    }
}

function validateDescription (input) {
    if (input === '') {
        return 'Please fill your element description, for example: "My awesome Web Component using Polymer".';
    }
}

module.exports = ElementGenerator;
<|endoftext|>