<commit_before>// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0

function wuvtLive(trackmanUrl) {
    $.ajax({
        'url': trackmanUrl + "/api/now_playing",
        'dataType': 'json',
    }).done(function(data) {
        $('#current_track').text(data['track']['artist'] + " - " +
                data['track']['title']);
        addDJLink2('#current_dj', data['dj']);
    });

    if(typeof EventSource == 'undefined') {
        // cannot use server-sent events, boo
        return;
    }

    var source = new EventSource(trackmanUrl + "/live");
    source.onmessage = function(ev) {
        msg = JSON.parse(ev.data);

        switch(msg['event']) {
            case 'track_change':
            case 'track_delete':
            case 'track_edit':
                var track = msg['tracklog']['track'];
                $('#current_track').text(track['artist'] + " - " +
                        track['title']);
                addDJLink('#current_dj', msg['tracklog']);
                break;
        }

        switch(msg['event']) {
            case 'track_change':
            case 'track_delete':
            case 'track_edit':
                if($('#last15tracks').length) {
                    // reload the last 15 tracks page
                    $.ajax({
                        'url': '/last15',
                        'dataType': 'html',
                    }).done(function(data) {
                        var doc = $('
').append($.parseHTML(data));

                        $('#last15tracks').html(doc.find('#last15tracks > *'));
                        $.each($('#last15tracks a'), function(i, item) {
                            makeAjaxLink(item);
                        });

                        initLocalDates();
                    });
                }
                break;
        }
    };
}

function addDJLink(elem, tracklog) {
    if(tracklog['dj_visible']) {
        var link = document.createElement('a');
        link.href = '/playlists/dj/' + tracklog['dj_id'];
        $(link).text(tracklog['dj']);
        makeAjaxLink(link);

        $(elem).html(link);
    }
    else {
        $(elem).text(tracklog['dj']);
    }
}

function addDJLink2(elem, dj) {
    if(dj['visible']) {
        var link = document.createElement('a');
        link.href = '/playlists/dj/' + dj['id'];
        $(link).text(dj['airname']);
        makeAjaxLink(link);

        $(elem).html(link);
    }
    else {
        $(elem).text(dj['airname']);
    }
}

function makeAjaxLink(item) {
    var absoluteRegex = new RegExp("^([a-z]+://|//)");
    var domainRegex = new RegExp(location.host);
    var staticRegex = new RegExp("/static/");
    var href = $(item).attr('href');

    if(!href || href.charAt(0) == '#' || staticRegex.test(href) ||
       (absoluteRegex.test(href) && !domainRegex.test(href))) {
        return;
    }

    if($(item).attr('rel') == 'feed') {
        return;
    }

    $(item).click(function(ev) {
        path = $(this).attr('href');
        window.history.pushState({'path': path}, item.innerText, path);
        loadPage(path);

        return false;
    });
}

function loadScriptFromList(scripts, i) {
    var deferred = $.ajax({
        url: scripts[i],
        dataType: "script",
        cache: true
    });

    if(i < scripts.length - 1) {
        deferred.then(function() {
            loadScriptFromList(scripts, i + 1);
        });
    }
}

function loadPage(path) {
    $.ajax({
        'url': path,
        'dataType': 'html',
    }).done(function(data) {
        updatePage($.parseHTML(data, document, true));
    }).fail(function(data) {
        if(data.status < 500) {
            updatePage($.parseHTML(data.responseText, document, true));
        } else {
            $('title').text("Internal Server Error");
            $('#content').html("

Internal Server Error

We apologize for the inconvenience. Please try your request again later.

");
        }
    });
}

function updatePage(srcDoc) {
    var doc = $('
').append(srcDoc);

    $('title').text(doc.find('title').text());
    $('#side').html(doc.find('#side > *'));
    $('#content').html(doc.find('#content > *'));

    $.each($('#side a'), function(i, item){makeAjaxLink(item);});
    $.each($('#content a'), function(i, item){makeAjaxLink(item);});

    // load scripts if necessary
    var scripts = [];
    $.each(doc.find('#extra_js script'), function(i, item) {
        scripts.push(item.src);
    });
    if(scripts.length > 0) {
        loadScriptFromList(scripts, 0);
    }

    $(document).trigger('pageChange');
    initLocalDates();
}

function progressTick() {
    var pageWidth = $('#progress_bar').offsetParent().width();
    var dist = Math.floor((Math.random() + 1) * 10);
    var newWidth = $('#progress_bar').width() + dist;
    if(newWidth / pageWidth < 1) {
        $('#progress_bar').width(newWidth);
    }
    else {
        $('#progress_bar').width('0.1%');
    }
}

function initProgressBar() {
    var progress = document.createElement('div');
    progress.id = 'progress_bar';
    $('body').append(progress);

    var progressTimer = setInterval(progressTick, 100);

    $(document).bind('ajaxStart', function() {
        clearInterval(progressTimer);
        progressTimer = setInterval(progressTick, 100);

        $('#progress_bar').width('0.1%');
        $('#progress_bar').fadeIn('fast');
    }).bind('ajaxComplete', function() {
        clearInterval(progressTimer);

        $('#progress_bar').width('100%');
        $('#progress_bar').fadeOut('fast');
    });

    $(window).bind('load', function() {
        clearInterval(progressTimer);

        $('#progress_bar').width('100%');
        $('#progress_bar').fadeOut('fast');
    });
}

function initAjaxLinks() {
    initProgressBar();

    makeAjaxLink($('#nowplaying a'));
    makeAjaxLink($('header #mainheader h1 a'));
    makeAjaxLink($('#radiothon_banner a'));
    $.each($('#bubble a'), function(i, item){makeAjaxLink(item);});
    $.each($('nav a'), function(i, item){makeAjaxLink(item);});
    $.each($('#content a'), function(i, item){makeAjaxLink(item);});
    $.each($('body > footer a'), function(i, item){makeAjaxLink(item);});

    // replace state in history, so going back to the first page works
    window.history.replaceState({'path': location.pathname}, window.title);

    window.onpopstate = function(ev) {
        loadPage(ev.state.path);
    };
}

function initLocalDates() {
    $('time').each(function() {
        var datestr = $(this).attr('datetime');
        var format = $(this).attr('data-format');
        var m = moment(datestr);
        if(m.isValid()) {
            $(this).text(m.format(format));
            $(this).attr('title', m.format('LLLL'));
        }
    });
}

// @license-end
<commit_msg>Try to use customized 500 error page but fall back<commit_after>// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0

function wuvtLive(trackmanUrl) {
    $.ajax({
        'url': trackmanUrl + "/api/now_playing",
        'dataType': 'json',
    }).done(function(data) {
        $('#current_track').text(data['track']['artist'] + " - " +
                data['track']['title']);
        addDJLink2('#current_dj', data['dj']);
    });

    if(typeof EventSource == 'undefined') {
        // cannot use server-sent events, boo
        return;
    }

    var source = new EventSource(trackmanUrl + "/live");
    source.onmessage = function(ev) {
        msg = JSON.parse(ev.data);

        switch(msg['event']) {
            case 'track_change':
            case 'track_delete':
            case 'track_edit':
                var track = msg['tracklog']['track'];
                $('#current_track').text(track['artist'] + " - " +
                        track['title']);
                addDJLink('#current_dj', msg['tracklog']);
                break;
        }

        switch(msg['event']) {
            case 'track_change':
            case 'track_delete':
            case 'track_edit':
                if($('#last15tracks').length) {
                    // reload the last 15 tracks page
                    $.ajax({
                        'url': '/last15',
                        'dataType': 'html',
                    }).done(function(data) {
                        var doc = $('
').append($.parseHTML(data));

                        $('#last15tracks').html(doc.find('#last15tracks > *'));
                        $.each($('#last15tracks a'), function(i, item) {
                            makeAjaxLink(item);
                        });

                        initLocalDates();
                    });
                }
                break;
        }
    };
}

function addDJLink(elem, tracklog) {
    if(tracklog['dj_visible']) {
        var link = document.createElement('a');
        link.href = '/playlists/dj/' + tracklog['dj_id'];
        $(link).text(tracklog['dj']);
        makeAjaxLink(link);

        $(elem).html(link);
    }
    else {
        $(elem).text(tracklog['dj']);
    }
}

function addDJLink2(elem, dj) {
    if(dj['visible']) {
        var link = document.createElement('a');
        link.href = '/playlists/dj/' + dj['id'];
        $(link).text(dj['airname']);
        makeAjaxLink(link);

        $(elem).html(link);
    }
    else {
        $(elem).text(dj['airname']);
    }
}

function makeAjaxLink(item) {
    var absoluteRegex = new RegExp("^([a-z]+://|//)");
    var domainRegex = new RegExp(location.host);
    var staticRegex = new RegExp("/static/");
    var href = $(item).attr('href');

    if(!href || href.charAt(0) == '#' || staticRegex.test(href) ||
       (absoluteRegex.test(href) && !domainRegex.test(href))) {
        return;
    }

    if($(item).attr('rel') == 'feed') {
        return;
    }

    $(item).click(function(ev) {
        path = $(this).attr('href');
        window.history.pushState({'path': path}, item.innerText, path);
        loadPage(path);

        return false;
    });
}

function loadScriptFromList(scripts, i) {
    var deferred = $.ajax({
        url: scripts[i],
        dataType: "script",
        cache: true
    });

    if(i < scripts.length - 1) {
        deferred.then(function() {
            loadScriptFromList(scripts, i + 1);
        });
    }
}

function loadPage(path) {
    $.ajax({
        'url': path,
        'dataType': 'html',
    }).done(function(data) {
        updatePage($.parseHTML(data, document, true));
    }).fail(function(data) {
        var parsedDoc = $.parseHTML(data.responseText, document, true);
        if(data.status < 500) {
            updatePage(parsedDoc);
        } else {
            var doc = $('
').append(parsedDoc);
            if(doc.find('title').text().indexOf('Server Error') > 0) {
                updatePage(parsedDoc);
            } else {
                $('title').text("Internal Server Error");
                $('#content').html("

Internal Server Error

We apologize for the inconvenience. Please try your request again later.

");
            }
        }
    });
}

function updatePage(srcDoc) {
    var doc = $('
').append(srcDoc);

    $('title').text(doc.find('title').text());
    $('#side').html(doc.find('#side > *'));
    $('#content').html(doc.find('#content > *'));

    $.each($('#side a'), function(i, item){makeAjaxLink(item);});
    $.each($('#content a'), function(i, item){makeAjaxLink(item);});

    // load scripts if necessary
    var scripts = [];
    $.each(doc.find('#extra_js script'), function(i, item) {
        scripts.push(item.src);
    });
    if(scripts.length > 0) {
        loadScriptFromList(scripts, 0);
    }

    $(document).trigger('pageChange');
    initLocalDates();
}

function progressTick() {
    var pageWidth = $('#progress_bar').offsetParent().width();
    var dist = Math.floor((Math.random() + 1) * 10);
    var newWidth = $('#progress_bar').width() + dist;
    if(newWidth / pageWidth < 1) {
        $('#progress_bar').width(newWidth);
    }
    else {
        $('#progress_bar').width('0.1%');
    }
}

function initProgressBar() {
    var progress = document.createElement('div');
    progress.id = 'progress_bar';
    $('body').append(progress);

    var progressTimer = setInterval(progressTick, 100);

    $(document).bind('ajaxStart', function() {
        clearInterval(progressTimer);
        progressTimer = setInterval(progressTick, 100);

        $('#progress_bar').width('0.1%');
        $('#progress_bar').fadeIn('fast');
    }).bind('ajaxComplete', function() {
        clearInterval(progressTimer);

        $('#progress_bar').width('100%');
        $('#progress_bar').fadeOut('fast');
    });

    $(window).bind('load', function() {
        clearInterval(progressTimer);

        $('#progress_bar').width('100%');
        $('#progress_bar').fadeOut('fast');
    });
}

function initAjaxLinks() {
    initProgressBar();

    makeAjaxLink($('#nowplaying a'));
    makeAjaxLink($('header #mainheader h1 a'));
    makeAjaxLink($('#radiothon_banner a'));
    $.each($('#bubble a'), function(i, item){makeAjaxLink(item);});
    $.each($('nav a'), function(i, item){makeAjaxLink(item);});
    $.each($('#content a'), function(i, item){makeAjaxLink(item);});
    $.each($('body > footer a'), function(i, item){makeAjaxLink(item);});

    // replace state in history, so going back to the first page works
    window.history.replaceState({'path': location.pathname}, window.title);

    window.onpopstate = function(ev) {
        loadPage(ev.state.path);
    };
}

function initLocalDates() {
    $('time').each(function() {
        var datestr = $(this).attr('datetime');
        var format = $(this).attr('data-format');
        var m = moment(datestr);
        if(m.isValid()) {
            $(this).text(m.format(format));
            $(this).attr('title', m.format('LLLL'));
        }
    });
}

// @license-end
<|endoftext|>


<commit_before>function FingerprintAuth() {
}

FingerprintAuth.prototype.show = function (params, successCallback, errorCallback) {
    cordova.exec(
        successCallback,
        errorCallback,
        "FingerprintAuth",  // Java Class
        "authenticate", // action
        [ // Array of arguments to pass to the Java class
            params
        ]
    );
};

FingerprintAuth.prototype.isAvailable = function (successCallback, errorCallback) {
    cordova.exec(
        successCallback,
        errorCallback,
        "FingerprintAuth",  // Java Class
        "availability", // action
        [{}]
    );
};

FingerprintAuth = new FingerprintAuth();
module.exports = FingerprintAuth;<commit_msg>Adding `init` & `delete` functions<commit_after>cordova.define("cordova-plugin-android-fingerprint-auth.FingerprintAuth", function(require, exports, module) {
  function FingerprintAuth() {
  }

  FingerprintAuth.prototype.show = function (params, successCallback, errorCallback) {
      cordova.exec(
          successCallback,
          errorCallback,
          "FingerprintAuth",  // Java Class
          "authenticate", // action
          [ // Array of arguments to pass to the Java class
              params
          ]
      );
  };

  FingerprintAuth.prototype.init = function (params, successCallback, errorCallback) {
      cordova.exec(
          successCallback,
          errorCallback,
          "FingerprintAuth",  // Java Class
          "init", // action
          [ // Array of arguments to pass to the Java class
              params
          ]
      );
  };

  FingerprintAuth.prototype.delete = function (params, successCallback, errorCallback) {
      cordova.exec(
          successCallback,
          errorCallback,
          "FingerprintAuth",  // Java Class
          "delete", // action
          [ // Array of arguments to pass to the Java class
              params
          ]
      );
  };

  FingerprintAuth.prototype.isAvailable = function (successCallback, errorCallback) {
      cordova.exec(
          successCallback,
          errorCallback,
          "FingerprintAuth",  // Java Class
          "availability", // action
          [{}]
      );
  };

  FingerprintAuth = new FingerprintAuth();
  module.exports = FingerprintAuth;
});
<|endoftext|>


<commit_before>// Client-Side Applications had better use implicit_grant
// see here -> http://wiki.dev.app.360.cn/index.php?title=OAuth2.0%E6%96%87%E6%A1%A3
// But as the website's implicit granted access token expires in such a short time (1200s currently),
// it is unrealistic to take this approach in a client app.
// So here I implemented an OAuth service with the authorization code approach.
import qs from 'nodelibs/querystring';
import secrets from '../../secrets.json!';

class LoginController {
  constructor($scope, $http) {
    // ionic 对 controller as 支持有问题 https://github.com/driftyco/ionic/issues/3058
    $scope.ctrl = this;

    this.$scope = $scope;
    this.$http = $http;

    this.authorizeEndpoint = 'http://login.cc98.org/oauth/authorize';
    this.tokenEndpoint = 'http://login.cc98.org/oauth/token';
    //this.tokenEndpoint = 'http://127.0.0.1:3000/';

    this.refreshTokenExpiry = 1200; // 目前 98 的实现有问题，时间太短，后续会改的
  }

  /**
   * OAuth 登录，采用 Authorization Code 方式
   * @todo 增加 state 参数
   */
  login() {
    this._toggleKeyboardAccessoryBar({show: true});

    var authorizeUrl = this.authorizeEndpoint + '?' + qs.stringify({
      client_id: secrets.client_id,
      redirect_uri: secrets.redirect_uri,
      response_type: 'code',
      grant_type: 'authorization_code',
      scope: 'all*'
    });
    var ref = window.open(authorizeUrl, '_blank');
    ref.addEventListener('loadstart', (evt) => {
      if (evt.url.startsWith(secrets.redirect_uri)) {
        var {code, error} = qs.parse(evt.url.split('?')[1]);
        this._toggleKeyboardAccessoryBar({show: false});

        if (error) {
          alert('Failed to get authorization code!');
        } else {
          this._getToken(code);
        }

        //ref.close();
      }
    });
  }

  _getTokens(code) {
    this.$http({
      method: 'POST',
      url: this.tokenEndpoint,
      data: {
        grant_type: 'authorization_code',
        client_id: secrets.client_id,
        client_secret: secrets.client_secret,
        redirect_uri: secrets.redirect_uri,
        code: code
      },
      headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
      transformRequest: function(obj) {
        var str = [];
        for(var p in obj){
          str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
        }
        return str.join('&');
      }
    }).success(function(data) {
      var {access_token, token_type, expires_in, refresh_token} = data;
    }).error(function(err) {
      alert('获取 Access Token 错误');
    });
  }

  /**
   * toggle keyboard accessory bar
   * @param  {Object}  params
   * @param  {boolean} params.show set this to true to show the bar or false to hide
   */
  _toggleKeyboardAccessoryBar(params) {
    // 重新关掉键盘的 AccessoryBar，毕竟只有填表单时用得到
    if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
      window.cordova.plugins.Keyboard.hideKeyboardAccessoryBar(!params.show);
    }
  }
}

LoginController.$inject = ['$scope', '$http'];

export default LoginController;

/*
var requestToken = "";
var accessToken = "";
var clientId = "client_id_here";
var clientSecret = "client_secret_here";
var refreshTokenExpiry = 1200;  // 目前 98 的实现有问题，后续会改的

var exampleApp = angular.module('example', ['ionic'])
    .config(function($stateProvider, $urlRouterProvider) {
        $stateProvider
            .state('login', {
                url: '/login',
                templateUrl: 'templates/login.html',
                controller: 'LoginController'
            })
            .state('secure', {
                url: '/secure',
                templateUrl: 'templates/secure.html',
                controller: 'SecureController'
            });
        $urlRouterProvider.otherwise('/login');
    });

exampleApp.controller('LoginController', function($scope, $http, $location) {

    $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

    $scope.login = function() {
        var ref = window.open('https://accounts.google.com/o/oauth2/auth?client_id=' + clientId + '&redirect_uri=http://localhost/callback&scope=https://www.googleapis.com/auth/urlshortener&approval_prompt=force&response_type=code&access_type=offline', '_blank', 'location=no');
        ref.addEventListener('loadstart', function(event) {
            if((event.url).startsWith("http://localhost/callback")) {
                requestToken = (event.url).split("code=")[1];
                $http({method: "post", url: "https://accounts.google.com/o/oauth2/token", data: "client_id=" + clientId + "&client_secret=" + clientSecret + "&redirect_uri=http://localhost/callback" + "&grant_type=authorization_code" + "&code=" + requestToken })
                    .success(function(data) {
                        accessToken = data.access_token;
                        $location.path("/secure");
                    })
                    .error(function(data, status) {
                        alert("ERROR: " + data);
                    });
                ref.close();
            }
        });
    }

    if (typeof String.prototype.startsWith != 'function') {
        String.prototype.startsWith = function (str){
            return this.indexOf(str) == 0;
        };
    }

});

exampleApp.controller('SecureController', function($scope, $http) {

    $scope.accessToken = accessToken;

});
 */
<commit_msg>Move scope to secrets.json<commit_after>// Client-Side Applications had better use implicit_grant
// see here -> http://wiki.dev.app.360.cn/index.php?title=OAuth2.0%E6%96%87%E6%A1%A3
// But as the website's implicit granted access token expires in such a short time (1200s currently),
// it is unrealistic to take this approach in a client app.
// So here I implemented an OAuth service with the authorization code approach.
import qs from 'nodelibs/querystring';
import secrets from '../../secrets.json!';

class LoginController {
  constructor($scope, $http) {
    // ionic 对 controller as 支持有问题 https://github.com/driftyco/ionic/issues/3058
    $scope.ctrl = this;

    this.$scope = $scope;
    this.$http = $http;

    this.authorizeEndpoint = 'http://login.cc98.org/oauth/authorize';
    this.tokenEndpoint = 'http://login.cc98.org/oauth/token';
    //this.tokenEndpoint = 'http://127.0.0.1:3000/';

    this.refreshTokenExpiry = 1200; // 目前 98 的实现有问题，时间太短，后续会改的
  }

  /**
   * OAuth 登录，采用 Authorization Code 方式
   * @todo 增加 state 参数
   */
  login() {
    this._toggleKeyboardAccessoryBar({show: true});

    var authorizeUrl = this.authorizeEndpoint + '?' + qs.stringify({
      response_type: 'code',
      grant_type: 'authorization_code',
      client_id: secrets.client_id,
      redirect_uri: secrets.redirect_uri,
      scope: secrets.scope
    });
    var ref = window.open(authorizeUrl, '_blank');
    ref.addEventListener('loadstart', (evt) => {
      if (evt.url.startsWith(secrets.redirect_uri)) {
        var {code, error} = qs.parse(evt.url.split('?')[1]);
        this._toggleKeyboardAccessoryBar({show: false});

        if (error) {
          alert('Failed to get authorization code!');
        } else {
          this._getToken(code);
        }

        //ref.close();
      }
    });
  }

  _getTokens(code) {
    this.$http({
      method: 'POST',
      url: this.tokenEndpoint,
      data: {
        grant_type: 'authorization_code',
        client_id: secrets.client_id,
        client_secret: secrets.client_secret,
        redirect_uri: secrets.redirect_uri,
        code: code
      },
      headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
      transformRequest: function(obj) {
        var str = [];
        for(var p in obj){
          str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
        }
        return str.join('&');
      }
    }).success(function(data) {
      var {access_token, token_type, expires_in, refresh_token} = data;
    }).error(function(err) {
      alert('获取 Access Token 错误');
    });
  }

  /**
   * toggle keyboard accessory bar
   * @param  {Object}  params
   * @param  {boolean} params.show set this to true to show the bar or false to hide
   */
  _toggleKeyboardAccessoryBar(params) {
    // 重新关掉键盘的 AccessoryBar，毕竟只有填表单时用得到
    if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
      window.cordova.plugins.Keyboard.hideKeyboardAccessoryBar(!params.show);
    }
  }
}

LoginController.$inject = ['$scope', '$http'];

export default LoginController;

/*
var requestToken = "";
var accessToken = "";
var clientId = "client_id_here";
var clientSecret = "client_secret_here";
var refreshTokenExpiry = 1200;  // 目前 98 的实现有问题，后续会改的

var exampleApp = angular.module('example', ['ionic'])
    .config(function($stateProvider, $urlRouterProvider) {
        $stateProvider
            .state('login', {
                url: '/login',
                templateUrl: 'templates/login.html',
                controller: 'LoginController'
            })
            .state('secure', {
                url: '/secure',
                templateUrl: 'templates/secure.html',
                controller: 'SecureController'
            });
        $urlRouterProvider.otherwise('/login');
    });

exampleApp.controller('LoginController', function($scope, $http, $location) {

    $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

    $scope.login = function() {
        var ref = window.open('https://accounts.google.com/o/oauth2/auth?client_id=' + clientId + '&redirect_uri=http://localhost/callback&scope=https://www.googleapis.com/auth/urlshortener&approval_prompt=force&response_type=code&access_type=offline', '_blank', 'location=no');
        ref.addEventListener('loadstart', function(event) {
            if((event.url).startsWith("http://localhost/callback")) {
                requestToken = (event.url).split("code=")[1];
                $http({method: "post", url: "https://accounts.google.com/o/oauth2/token", data: "client_id=" + clientId + "&client_secret=" + clientSecret + "&redirect_uri=http://localhost/callback" + "&grant_type=authorization_code" + "&code=" + requestToken })
                    .success(function(data) {
                        accessToken = data.access_token;
                        $location.path("/secure");
                    })
                    .error(function(data, status) {
                        alert("ERROR: " + data);
                    });
                ref.close();
            }
        });
    }

    if (typeof String.prototype.startsWith != 'function') {
        String.prototype.startsWith = function (str){
            return this.indexOf(str) == 0;
        };
    }

});

exampleApp.controller('SecureController', function($scope, $http) {

    $scope.accessToken = accessToken;

});
 */
<|endoftext|>


<commit_before>/*
    Copyright 2013-2014 appPlant UG

    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
*/

var exec    = require('cordova/exec'),
    channel = require('cordova/channel');

// Called before 'deviceready' listener will be called
channel.onCordovaReady.subscribe(function () {
    // Device plugin is ready now
    channel.onCordovaInfoReady.subscribe(function () {
        // Set the defaults
        exports.setDefaults({});
    });
});

/**
 * @private
 *
 * Flag indicated if the mode is enabled.
 */
exports._isEnabled = false;

/**
 * @private
 *
 * Flag indicated if the mode is active.
 */
exports._isActive = false;

/**
 * @private
 *
 * Default values of all available options.
 */
exports._defaults = {
    title:  'App is running in background',
    text:   'Doing heavy tasks.',
    ticker: 'Running in background',
    resume: true,
    silent: false,
    color:  undefined,
    icon:   'icon'
};

/**
 * Activates the background mode. When activated the application
 * will be prevented from going to sleep while in background
 * for the next time.
 */
exports.enable = function () {
    this._isEnabled = true;
    cordova.exec(null, null, 'BackgroundMode', 'enable', []);
};

/**
 * Deactivates the background mode. When deactivated the application
 * will not stay awake while in background.
 */
exports.disable = function () {
    this._isEnabled = false;
    cordova.exec(null, null, 'BackgroundMode', 'disable', []);
};

/**
 * Deactivates the background mode. When deactivated the application
 * will not stay awake while in background.
 */
exports.restart = function () {
    cordova.exec(null, null, 'BackgroundMode', 'restart', []);
};

/**
 * Deactivates the background mode. When deactivated the application
 * will not stay awake while in background.
 */
exports.makeNotificationCusts = function () {
    alert('Load script')
    cordova.exec(null, null, 'BackgroundMode', 'makeNotificationCusts', []);
};

/**
 * List of all available options with their default value.
 *
 * @return {Object}
 */
exports.getDefaults = function () {
    return this._defaults;
};

/**
 * Overwrite default settings
 *
 * @param {Object} overrides
 *      Dict of options which shall be overridden
 */
exports.setDefaults = function (overrides) {
    var defaults = this.getDefaults();

    for (var key in defaults) {
        if (overrides.hasOwnProperty(key)) {
            defaults[key] = overrides[key];
        }
    }

    if ((device.platform == 'Android') || (device.platform == 'Windows')){
        cordova.exec(null, null, 'BackgroundMode', 'configure', [defaults, false]);
    }
};

/**
 * Configures the notification settings for Android.
 * Will be merged with the defaults.
 *
 * @param {Object} options
 *      Dict with key/value pairs
 */
exports.configure = function (options) {
    var settings = this.mergeWithDefaults(options);

    if ((device.platform == 'Android') || (device.platform == 'Windows')){
        cordova.exec(null, null, 'BackgroundMode', 'configure', [settings, true]);
    }
};

/**
 * If the mode is enabled or disabled.
 *
 * @return {Boolean}
 */
exports.isEnabled = function () {
    return this._isEnabled;
};

/**
 * If the mode is active.
 *
 * @return {Boolean}
 */
exports.isActive = function () {
    return this._isActive;
};

/**
 * Called when the background mode has been activated.
 */
exports.onactivate = function () {};

/**
 * Called when the background mode has been deaktivated.
 */
exports.ondeactivate = function () {};

/**
 * Called when the background mode could not been activated.
 *
 * @param {Integer} errorCode
 *      Error code which describes the error
 */
exports.onfailure = function () {};

/**
 * @private
 *
 * Merge settings with default values.
 *
 * @param {Object} options
 *      The custom options
 *
 * @return {Object}
 *      Default values merged
 *      with custom values
 */
exports.mergeWithDefaults = function (options) {
    var defaults = this.getDefaults();

    for (var key in defaults) {
        if (!options.hasOwnProperty(key)) {
            options[key] = defaults[key];
            continue;
        }
    }

    return options;
};
<commit_msg>Update background-mode.js<commit_after>/*
    Copyright 2013-2014 appPlant UG

    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
*/

var exec    = require('cordova/exec'),
    channel = require('cordova/channel');

// Called before 'deviceready' listener will be called
channel.onCordovaReady.subscribe(function () {
    // Device plugin is ready now
    channel.onCordovaInfoReady.subscribe(function () {
        // Set the defaults
        exports.setDefaults({});
    });
});

/**
 * @private
 *
 * Flag indicated if the mode is enabled.
 */
exports._isEnabled = false;

/**
 * @private
 *
 * Flag indicated if the mode is active.
 */
exports._isActive = false;

/**
 * @private
 *
 * Default values of all available options.
 */
exports._defaults = {
    title:  'App is running in background',
    text:   'Doing heavy tasks.',
    ticker: 'Running in background',
    resume: true,
    silent: false,
    color:  undefined,
    icon:   'icon'
};

/**
 * Activates the background mode. When activated the application
 * will be prevented from going to sleep while in background
 * for the next time.
 */
exports.enable = function () {
    this._isEnabled = true;
    cordova.exec(null, null, 'BackgroundMode', 'enable', []);
};

/**
 * Deactivates the background mode. When deactivated the application
 * will not stay awake while in background.
 */
exports.disable = function () {
    this._isEnabled = false;
    cordova.exec(null, null, 'BackgroundMode', 'disable', []);
};

/**
 * Deactivates the background mode. When deactivated the application
 * will not stay awake while in background.
 */
exports.restart = function () {
    cordova.exec(null, null, 'BackgroundMode', 'restart', []);
};

/**
 * Deactivates the background mode. When deactivated the application
 * will not stay awake while in background.
 */
exports.makeNotificationCusts = function (options) {
    alert('Load script');
    cordova.exec(null, null, 'BackgroundMode', 'makeNotificationCusts', [options, true]);
};

/**
 * List of all available options with their default value.
 *
 * @return {Object}
 */
exports.getDefaults = function () {
    return this._defaults;
};

/**
 * Overwrite default settings
 *
 * @param {Object} overrides
 *      Dict of options which shall be overridden
 */
exports.setDefaults = function (overrides) {
    var defaults = this.getDefaults();

    for (var key in defaults) {
        if (overrides.hasOwnProperty(key)) {
            defaults[key] = overrides[key];
        }
    }

    if ((device.platform == 'Android') || (device.platform == 'Windows')){
        cordova.exec(null, null, 'BackgroundMode', 'configure', [defaults, false]);
    }
};

/**
 * Configures the notification settings for Android.
 * Will be merged with the defaults.
 *
 * @param {Object} options
 *      Dict with key/value pairs
 */
exports.configure = function (options) {
    var settings = this.mergeWithDefaults(options);

    if ((device.platform == 'Android') || (device.platform == 'Windows')){
        cordova.exec(null, null, 'BackgroundMode', 'configure', [settings, true]);
    }
};

/**
 * If the mode is enabled or disabled.
 *
 * @return {Boolean}
 */
exports.isEnabled = function () {
    return this._isEnabled;
};

/**
 * If the mode is active.
 *
 * @return {Boolean}
 */
exports.isActive = function () {
    return this._isActive;
};

/**
 * Called when the background mode has been activated.
 */
exports.onactivate = function () {};

/**
 * Called when the background mode has been deaktivated.
 */
exports.ondeactivate = function () {};

/**
 * Called when the background mode could not been activated.
 *
 * @param {Integer} errorCode
 *      Error code which describes the error
 */
exports.onfailure = function () {};

/**
 * @private
 *
 * Merge settings with default values.
 *
 * @param {Object} options
 *      The custom options
 *
 * @return {Object}
 *      Default values merged
 *      with custom values
 */
exports.mergeWithDefaults = function (options) {
    var defaults = this.getDefaults();

    for (var key in defaults) {
        if (!options.hasOwnProperty(key)) {
            options[key] = defaults[key];
            continue;
        }
    }

    return options;
};
<|endoftext|>


<commit_before>define([], function () {
    var tree = {};

    // FIXME this isn't being used
    var someElement = tree.some = function (root, predicate) {
        // take the index of the last element in the current root
        var last = root.childElementCount - 1;

        // it might be a leaf node
        if (last < 0) { return false; }

        // otherwise it has children
        while (last >= 0) {
            // check from back to front

            // check the node's children (depth first)
            // if the predicate tests true, return true
            if (tree.some(root.children[last], predicate)) {
                return true;
            } // otherwise none of the nodes inside it matched.

            // check the node itself
            if (predicate(root.children[last], last)) {
                return true;
            }
            last--;
        }
        return false;
    };

    // FIXME this isn't being used
    var someText = tree.someIncludingText = function (root, predicate) {
        // take the index of the last element in the current root
        var last = root.childNodes.length - 1;

        // it might be a leaf node
        if (last < 0) { return false; }

        // otherwise it has children
        while (last >= 0) {
            // check from back to front

            // check the node's children (depth first)
            // if the predicate tests true, return true
            if (tree.someIncludingText(root.childNodes[last], predicate)) {
                return true;
            } // otherwise none of the nodes inside it matched.

            // check the node itself
            if (predicate(root.childNodes[last], last)) {
                return true;
            }
            last--;
        }
        return false;
    };

    // FIXME not being used
    tree.findSameHierarchy = function (list, ancestor) {
        var i = 0;
        var success = true;
        var last = list.length - 1;
        var el;

        tree.someIncludingText(ancestor, function (e) {
            // don't out of bounds
            if (i > last) {
                // unsuccessful
                success = false;
                return true;
            }

            if (list[i] === (e.tagName||e.nodeName)) {

                if (i === last) {
                    el = e;
                    return true;
                }
                i++;
            } else {
                // hierarchy has changed, what should we do?
                success = false;
                return true; // terminate
            }
        });
        return success? el: false;
    };

    var indexOfNode = tree.indexOfNode = function (el) {
        if (!(el && el.parentNode)) {
            console.log("No parentNode found!");
            throw new Error('No parentNode found!');
        }
        return Array.prototype.indexOf.call(el.parentNode.childNodes, el);
    };

    // not being used internally, but is useful externally
    tree.contains = function (el, root) {
        return el && root.contains && root.contains(el);
    };

    var siblingCount = tree.siblingCount = function (el) {
        return el.parentNode.childNodes.length;
    };

    var childCount = tree.childCount = function (el) {
        return el.childNodes.length;
    };

    var parentsOf = tree.parentsOf = function (el, root) {
        var P = [];
        var p = el;
        while (p !== root) { P.push((p = p.parentNode)); }
        return P;
    };

    /* rightmost and leftmost return the deepest right and left
        leaf nodes of a tree
    */
    var rightmostNode = tree.rightmostNode = function (el) {
        var childNodeCount = childCount(el);
        if (!childNodeCount) { // no children
            return el; // return the element
        } else {
            return rightmostNode(el.childNodes[childNodeCount - 1]);
        }
    };

    var leftmostNode = tree.leftmostNode = function (el) {
        if (childCount(el)) {
            return leftmostNode(el.childNodes[0]);
        } else {
            return el;
        }
    };

    /* previousNode and nextNode traverse child elements of the dom
        in the order in which they appear when selected by a cursor.
        in particular, these algorithms traverse text nodes, not just tags
    */
    var previousNode = tree.previousNode = function (el, root) {
        if (!el || el === root) { return null; }

        var i = indexOfNode(el);
        if (!el.parentNode) { return null; }
        if (i === 0) {
            if (root && el.parentNode === root.childNodes[0]) { return null; }
            return rightmostNode(previousNode(el.parentNode));
        } else {
            return rightmostNode(el.parentNode.childNodes[i-1]);
        }
    };

    var nextNode = tree.nextNode = function (el, root) {
        if (!el || el === root) { return null; }
        var i = indexOfNode(el) + 1, // the index of the next node
            l = siblingCount(el);
        if (i === l) { // out of bounds
            if (el.parentNode === root) { return null; }
            return nextNode(el.parentNode, root);
        } else {
            return leftmostNode(el.parentNode.childNodes[i], root);
        }
    };

    var orderOfNodes = tree.orderOfNodes = function (a, b, root) {
        // b might not be supplied
        if (!b) { return; }
        // a and b might be the same element
        if (a === b) { return 0; }

        var cur = b;
        while (cur) {
            cur = previousNode(cur, root);
            // if you find 'a' while traversing backwards
            // they are in the expected order
            if (cur === a) { return 1; }
        }
        // otherwise
        return -1;
    };

    return tree;
});
<commit_msg>check if possible parent exists before checking if it contains another element<commit_after>define([], function () {
    var tree = {};

    // FIXME this isn't being used
    var someElement = tree.some = function (root, predicate) {
        // take the index of the last element in the current root
        var last = root.childElementCount - 1;

        // it might be a leaf node
        if (last < 0) { return false; }

        // otherwise it has children
        while (last >= 0) {
            // check from back to front

            // check the node's children (depth first)
            // if the predicate tests true, return true
            if (tree.some(root.children[last], predicate)) {
                return true;
            } // otherwise none of the nodes inside it matched.

            // check the node itself
            if (predicate(root.children[last], last)) {
                return true;
            }
            last--;
        }
        return false;
    };

    // FIXME this isn't being used
    var someText = tree.someIncludingText = function (root, predicate) {
        // take the index of the last element in the current root
        var last = root.childNodes.length - 1;

        // it might be a leaf node
        if (last < 0) { return false; }

        // otherwise it has children
        while (last >= 0) {
            // check from back to front

            // check the node's children (depth first)
            // if the predicate tests true, return true
            if (tree.someIncludingText(root.childNodes[last], predicate)) {
                return true;
            } // otherwise none of the nodes inside it matched.

            // check the node itself
            if (predicate(root.childNodes[last], last)) {
                return true;
            }
            last--;
        }
        return false;
    };

    // FIXME not being used
    tree.findSameHierarchy = function (list, ancestor) {
        var i = 0;
        var success = true;
        var last = list.length - 1;
        var el;

        tree.someIncludingText(ancestor, function (e) {
            // don't out of bounds
            if (i > last) {
                // unsuccessful
                success = false;
                return true;
            }

            if (list[i] === (e.tagName||e.nodeName)) {

                if (i === last) {
                    el = e;
                    return true;
                }
                i++;
            } else {
                // hierarchy has changed, what should we do?
                success = false;
                return true; // terminate
            }
        });
        return success? el: false;
    };

    var indexOfNode = tree.indexOfNode = function (el) {
        if (!(el && el.parentNode)) {
            console.log("No parentNode found!");
            throw new Error('No parentNode found!');
        }
        return Array.prototype.indexOf.call(el.parentNode.childNodes, el);
    };

    // not being used internally, but is useful externally
    tree.contains = function (el, root) {
        return el && root && root.contains && root.contains(el);
    };

    var siblingCount = tree.siblingCount = function (el) {
        return el.parentNode.childNodes.length;
    };

    var childCount = tree.childCount = function (el) {
        return el.childNodes.length;
    };

    var parentsOf = tree.parentsOf = function (el, root) {
        var P = [];
        var p = el;
        while (p !== root) { P.push((p = p.parentNode)); }
        return P;
    };

    /* rightmost and leftmost return the deepest right and left
        leaf nodes of a tree
    */
    var rightmostNode = tree.rightmostNode = function (el) {
        var childNodeCount = childCount(el);
        if (!childNodeCount) { // no children
            return el; // return the element
        } else {
            return rightmostNode(el.childNodes[childNodeCount - 1]);
        }
    };

    var leftmostNode = tree.leftmostNode = function (el) {
        if (childCount(el)) {
            return leftmostNode(el.childNodes[0]);
        } else {
            return el;
        }
    };

    /* previousNode and nextNode traverse child elements of the dom
        in the order in which they appear when selected by a cursor.
        in particular, these algorithms traverse text nodes, not just tags
    */
    var previousNode = tree.previousNode = function (el, root) {
        if (!el || el === root) { return null; }

        var i = indexOfNode(el);
        if (!el.parentNode) { return null; }
        if (i === 0) {
            if (root && el.parentNode === root.childNodes[0]) { return null; }
            return rightmostNode(previousNode(el.parentNode));
        } else {
            return rightmostNode(el.parentNode.childNodes[i-1]);
        }
    };

    var nextNode = tree.nextNode = function (el, root) {
        if (!el || el === root) { return null; }
        var i = indexOfNode(el) + 1, // the index of the next node
            l = siblingCount(el);
        if (i === l) { // out of bounds
            if (el.parentNode === root) { return null; }
            return nextNode(el.parentNode, root);
        } else {
            return leftmostNode(el.parentNode.childNodes[i], root);
        }
    };

    var orderOfNodes = tree.orderOfNodes = function (a, b, root) {
        // b might not be supplied
        if (!b) { return; }
        // a and b might be the same element
        if (a === b) { return 0; }

        var cur = b;
        while (cur) {
            cur = previousNode(cur, root);
            // if you find 'a' while traversing backwards
            // they are in the expected order
            if (cur === a) { return 1; }
        }
        // otherwise
        return -1;
    };

    return tree;
});
<|endoftext|>


<commit_before>import EventEmitter from 'eventemitter3';
import sleep from 'sleep-promise';
import Terminal from 'terminal.js';

import key from '../../utils/keyboard';
import {
  getWidth,
  indexOfWidth,
  substrWidth,
} from '../../utils/char';

import defaultConfig from './config';

const setIntevalUntil = (async (_func, _validate, _inteval) => {
  await sleep(_inteval);
  let ret = await _func();
  if (_validate(ret)) return ret;
  else return setIntevalUntil(_func, _validate, _inteval);
});

class Bot extends EventEmitter {
  static initialState = {
    login: false,
  };
  static forwardEvents = [
    'connect',
    'disconnect',
    'message',
    'error',
  ];
  constructor(config) {
    super();
    config = {...defaultConfig, ...config};

    this._term = new Terminal(config.terminal);
    this._state = { ...Bot.initialState };
    this._term.state.setMode('stringWidth', 'dbcs');

    let Socket;
    switch (config.protocol.toLowerCase()) {
      case 'websocket':
      case 'ws':
      case 'wss':
        Socket = require("../../core/socket").default;
        break;
      case 'telnet':
      case 'ssh':
      default:
        Socket = null;
    }

    if (Socket === null) {
      throw `Invalid protocol: ${config.protocol}`;
    }

    const socket = new Socket(config);
    socket.connect();

    Bot.forwardEvents.forEach(e => {
      socket.on(e, this.emit.bind(this, e));
    });
    socket
      .on('message', (msg) => {
        this._term.write(msg);
        this.emit('redraw', this._term.toString());
      })
      .on('disconnect', (close) => {
      })
      .on('error', (err) => {
        console.log(err);
      });
    this.socket = socket;
    this.config = config;
  }

  get state() {
    return {...this._state};
  }

  getLine = (n) => {
    return this._term.state.getLine(n);
  };

  async send(msg) {
    this.config.preventIdle && this.preventIdle();
    return new Promise(resolve => {
      this.socket.send(msg);
      this.once('message', msg => {
        resolve(msg);
      });
    });
  }

  preventIdle(timeout = 60) {
    clearTimeout(this.preventIdleHandler);
    this.preventIdleHandler = setTimeout(() => {
      this.send(`${key.CtrlU}${key.ArrowLeft}`);
    }, timeout * 1000);
  }

  async login(username, password) {
    if (this._state.login) return;
    username = username.replace(/,/g, '');
    await this.send(`${username},${key.Enter}${password}${key.Enter}`);
    let ret;
    while ((ret = await this._checkLogin()) === null) {
      await sleep(400);
    }
    if (ret) {
      const { _state: state } = this;
      state.login = true;
      state.position = {
        boardname: "",
      };
      this.emit('stateChange', this.state);
    }
    return ret;
  }

  async logout() {
    if (!this._state.login) return;
    await this.send(`${key.ArrowLeft.repeat(10)}${key.ArrowRight}y${key.Enter}`);
    this._state.login = false;
    this.emit('stateChange', this.state);
    return true;
  }

  async _checkLogin() {
    const { getLine } = this;

    if (getLine(21).str.includes("密碼不對或無此帳號")) {
      this.emit('login.failed');
      return false;
    } else if ((getLine(22).str+getLine(23).str).toLowerCase().includes("y/n")) {
    //} else if (getLine(22).str.includes("您想刪除其他重複登入的連線嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("請勿頻繁登入以免造成系統過度負荷")) {
      await this.send(`${key.Enter}`);
    } else if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(`${key.Enter}`);
    } else if (getLine(23).str.includes("您要刪除以上錯誤嘗試的記錄嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("我是")) {
      this.emit('login.success');
      return true;
    } else {
      await this.send(`q`);
    }
    return null;
  }

  async getArticles(boardname, offset=0) {
    await this.enterBoard(boardname);
    offset |= 0;
    if (offset > 0) {
      offset = Math.max(offset-9, 1);
      await this.send(`${key.End}${key.End}${offset}${key.Enter}`);
    }
    const { getLine } = this;
    let articles = [];
    for(let i=3; i<=22; i++) {
      const line = getLine(i).str;
      const article = {
        sn:     substrWidth('dbcs', line, 1,   7).trim() | 0,
        push:   substrWidth('dbcs', line, 9,   2).trim(),
        date:   substrWidth('dbcs', line, 11,  5).trim(),
        author: substrWidth('dbcs', line, 17, 12).trim(),
        status: substrWidth('dbcs', line, 30,  2).trim(),
        title:  substrWidth('dbcs', line, 32    ).trim(),
        fixed:  substrWidth('dbcs', line, 1,   7).trim().includes('★'),
      };
      articles.push(article);
    }
    // fix sn
    if (articles.length >= 2 && articles[0].sn === 0) {
      for(let i=1; i<articles.length; i++) {
        if (articles[i].sn !== 0) {
          articles[0].sn = articles[i].sn - i;
          break;
        }
      }
    }
    for(let i=1; i<articles.length; i++) {
      articles[i].sn = articles[i-1].sn+1;
    }
    await this.enterIndex();
    return articles.reverse();
  }

  async getArticle(boardname, sn) {
    await this.enterBoard(boardname);
    const { getLine } = this;

    await this.send(`${sn}${key.Enter}${key.Enter}`);

    let article = {
      sn,
      author:    substrWidth('dbcs', getLine(0).str, 7, 50).trim(),
      title:     substrWidth('dbcs', getLine(1).str, 7    ).trim(),
      timestamp: substrWidth('dbcs', getLine(2).str, 7    ).trim(),
      lines: [],
    };

    article.lines.push(getLine(0).str);

    while (!getLine(23).str.includes("100%")) {
      for(let i=1; i<23; i++) {
        article.lines.push(getLine(i).str);
      }
      await this.send(key.PgDown);
    }

    const lastLine = article.lines[article.lines.length-1];
    for(let i=0; i<23; i++) {
      if (getLine(i).str == lastLine) {
        for(let j=i+1; j<23; j++) {
          article.lines.push(getLine(j).str);
        }
        break;
      }
    }

    await this.enterIndex();
    return article;
  }

  async getFavorite() {
    await this.enterFavorite();
    const { getLine } = this;

    const favorites = [];

    for(let i=3; i<23; i++) {
      let line = getLine(i).str;
      if (line.trim() === '') break;
      let favorite = {
        bn:        substrWidth('dbcs', line,  3,  4).trim() | 0,
        read:      substrWidth('dbcs', line,  8,  2).trim() === '',
        boardname: substrWidth('dbcs', line, 10, 12).trim(),
        category:  substrWidth('dbcs', line, 23,  4).trim(),
        title:     substrWidth('dbcs', line, 30, 31),
        users:     substrWidth('dbcs', line, 62,  5).trim(),
        admin:     substrWidth('dbcs', line, 67    ).trim(),
        folder:    false,
        divider:   false,
      };
      switch (favorite.boardname) {
        case 'MyFavFolder':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            folder: true,
          };
          break;
        case '------------':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            divider: true,
          };
          break;
        default:
          break;
      }
      favorites.push(favorite);
    }

    await this.enterIndex();
    return favorites;
  }

  async enterIndex() {
    await this.send(`${key.ArrowLeft.repeat(10)}`);
    return true;
  }

  async enterBoard(boardname) {
    await this.send(`s${boardname}${key.Enter} ${key.Home}${key.End}`);
    boardname = boardname.toLowerCase();
    const { getLine } = this;

    if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    }
    if (getLine(0).str.toLowerCase().includes(`${boardname}`)) {
      this._state.position.boardname = boardname;
      this.emit('stateChange', this.state);
      return true;
    }
    return false;
  }

  async enterFavorite() {
    await this.send(`F${key.Enter}`);
    return true;
  }
}

export default Bot;
<commit_msg>add connect state in bot<commit_after>import EventEmitter from 'eventemitter3';
import sleep from 'sleep-promise';
import Terminal from 'terminal.js';

import key from '../../utils/keyboard';
import {
  getWidth,
  indexOfWidth,
  substrWidth,
} from '../../utils/char';

import defaultConfig from './config';

const setIntevalUntil = (async (_func, _validate, _inteval) => {
  await sleep(_inteval);
  let ret = await _func();
  if (_validate(ret)) return ret;
  else return setIntevalUntil(_func, _validate, _inteval);
});

class Bot extends EventEmitter {
  static initialState = {
    connect: false,
    login: false,
  };
  static forwardEvents = [
    'message',
    'error',
  ];
  constructor(config) {
    super();
    config = {...defaultConfig, ...config};

    this._term = new Terminal(config.terminal);
    this._state = { ...Bot.initialState };
    this._term.state.setMode('stringWidth', 'dbcs');

    let Socket;
    switch (config.protocol.toLowerCase()) {
      case 'websocket':
      case 'ws':
      case 'wss':
        Socket = require("../../core/socket").default;
        break;
      case 'telnet':
      case 'ssh':
      default:
        Socket = null;
    }

    if (Socket === null) {
      throw `Invalid protocol: ${config.protocol}`;
    }

    const socket = new Socket(config);
    socket.connect();

    Bot.forwardEvents.forEach(e => {
      socket.on(e, this.emit.bind(this, e));
    });
    socket
      .on('connect', (...args) => {
        this._state.connect = true;
        this.emit('connect', ...args);
        this.emit('stateChange', this.state);
      })
      .on('disconnect', (closeEvent, ...args) => {
        this._state.connect = false;
        this.emit('disconnect', closeEvent, ...args);
        this.emit('stateChange', this.state);
      })
      .on('message', (msg) => {
        this._term.write(msg);
        this.emit('redraw', this._term.toString());
      })
      .on('error', (err) => {
        console.log(err);
      });
    this.socket = socket;
    this.config = config;
  }

  get state() {
    return {...this._state};
  }

  getLine = (n) => {
    return this._term.state.getLine(n);
  };

  async send(msg) {
    this.config.preventIdle && this.preventIdle();
    return new Promise(resolve => {
      if (this.state.connect) {
        this.socket.send(msg);
        this.once('message', msg => {
          resolve(msg);
        });
      }
    });
  }

  preventIdle(timeout = 60) {
    clearTimeout(this.preventIdleHandler);
    if (this.state.login) {
      this.preventIdleHandler = setTimeout(() => {
        this.send(`${key.CtrlU}${key.ArrowLeft}`);
      }, timeout * 1000);
    }
  }

  async login(username, password) {
    if (this.state.login) return;
    username = username.replace(/,/g, '');
    await this.send(`${username},${key.Enter}${password}${key.Enter}`);
    let ret;
    while ((ret = await this._checkLogin()) === null) {
      await sleep(400);
    }
    if (ret) {
      const { _state: state } = this;
      state.login = true;
      state.position = {
        boardname: "",
      };
      this.emit('stateChange', this.state);
    }
    return ret;
  }

  async logout() {
    if (!this.state.login) return;
    await this.send(`${key.ArrowLeft.repeat(10)}${key.ArrowRight}y${key.Enter}`);
    this._state.login = false;
    this.emit('stateChange', this.state);
    return true;
  }

  async _checkLogin() {
    const { getLine } = this;

    if (getLine(21).str.includes("密碼不對或無此帳號")) {
      this.emit('login.failed');
      return false;
    } else if ((getLine(22).str+getLine(23).str).toLowerCase().includes("y/n")) {
    //} else if (getLine(22).str.includes("您想刪除其他重複登入的連線嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("請勿頻繁登入以免造成系統過度負荷")) {
      await this.send(`${key.Enter}`);
    } else if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(`${key.Enter}`);
    } else if (getLine(23).str.includes("您要刪除以上錯誤嘗試的記錄嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("我是")) {
      this.emit('login.success');
      return true;
    } else {
      await this.send(`q`);
    }
    return null;
  }

  async getArticles(boardname, offset=0) {
    await this.enterBoard(boardname);
    offset |= 0;
    if (offset > 0) {
      offset = Math.max(offset-9, 1);
      await this.send(`${key.End}${key.End}${offset}${key.Enter}`);
    }
    const { getLine } = this;
    let articles = [];
    for(let i=3; i<=22; i++) {
      const line = getLine(i).str;
      const article = {
        sn:     substrWidth('dbcs', line, 1,   7).trim() | 0,
        push:   substrWidth('dbcs', line, 9,   2).trim(),
        date:   substrWidth('dbcs', line, 11,  5).trim(),
        author: substrWidth('dbcs', line, 17, 12).trim(),
        status: substrWidth('dbcs', line, 30,  2).trim(),
        title:  substrWidth('dbcs', line, 32    ).trim(),
        fixed:  substrWidth('dbcs', line, 1,   7).trim().includes('★'),
      };
      articles.push(article);
    }
    // fix sn
    if (articles.length >= 2 && articles[0].sn === 0) {
      for(let i=1; i<articles.length; i++) {
        if (articles[i].sn !== 0) {
          articles[0].sn = articles[i].sn - i;
          break;
        }
      }
    }
    for(let i=1; i<articles.length; i++) {
      articles[i].sn = articles[i-1].sn+1;
    }
    await this.enterIndex();
    return articles.reverse();
  }

  async getArticle(boardname, sn) {
    await this.enterBoard(boardname);
    const { getLine } = this;

    await this.send(`${sn}${key.Enter}${key.Enter}`);

    let article = {
      sn,
      author:    substrWidth('dbcs', getLine(0).str, 7, 50).trim(),
      title:     substrWidth('dbcs', getLine(1).str, 7    ).trim(),
      timestamp: substrWidth('dbcs', getLine(2).str, 7    ).trim(),
      lines: [],
    };

    article.lines.push(getLine(0).str);

    while (!getLine(23).str.includes("100%")) {
      for(let i=1; i<23; i++) {
        article.lines.push(getLine(i).str);
      }
      await this.send(key.PgDown);
    }

    const lastLine = article.lines[article.lines.length-1];
    for(let i=0; i<23; i++) {
      if (getLine(i).str == lastLine) {
        for(let j=i+1; j<23; j++) {
          article.lines.push(getLine(j).str);
        }
        break;
      }
    }

    await this.enterIndex();
    return article;
  }

  async getFavorite() {
    await this.enterFavorite();
    const { getLine } = this;

    const favorites = [];

    for(let i=3; i<23; i++) {
      let line = getLine(i).str;
      if (line.trim() === '') break;
      let favorite = {
        bn:        substrWidth('dbcs', line,  3,  4).trim() | 0,
        read:      substrWidth('dbcs', line,  8,  2).trim() === '',
        boardname: substrWidth('dbcs', line, 10, 12).trim(),
        category:  substrWidth('dbcs', line, 23,  4).trim(),
        title:     substrWidth('dbcs', line, 30, 31),
        users:     substrWidth('dbcs', line, 62,  5).trim(),
        admin:     substrWidth('dbcs', line, 67    ).trim(),
        folder:    false,
        divider:   false,
      };
      switch (favorite.boardname) {
        case 'MyFavFolder':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            folder: true,
          };
          break;
        case '------------':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            divider: true,
          };
          break;
        default:
          break;
      }
      favorites.push(favorite);
    }

    await this.enterIndex();
    return favorites;
  }

  async enterIndex() {
    await this.send(`${key.ArrowLeft.repeat(10)}`);
    return true;
  }

  async enterBoard(boardname) {
    await this.send(`s${boardname}${key.Enter} ${key.Home}${key.End}`);
    boardname = boardname.toLowerCase();
    const { getLine } = this;

    if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    }
    if (getLine(0).str.toLowerCase().includes(`${boardname}`)) {
      this._state.position.boardname = boardname;
      this.emit('stateChange', this.state);
      return true;
    }
    return false;
  }

  async enterFavorite() {
    await this.send(`F${key.Enter}`);
    return true;
  }
}

export default Bot;
<|endoftext|>


<commit_before>angular.module('citizen-engagement.newIssueCtrl', [])

    .factory("CameraService", function ($q) {
        return {
            getPicture: function (options) {
                var deferred = $q.defer();

                navigator.camera.getPicture(function (result) {
                    // do any magic you need
                    deferred.resolve(result);
                }, function (err) {
                    deferred.reject(err);
                }, options);

                return deferred.promise;
            }
        }
    })
    .controller('NewIssueCtrl',
        function ($scope, $http, apiUrl) {
            $scope.loadIssueTypes = function () {
                console.log("ok");
                $http.get(apiUrl + '/issueTypes').success(function (issueTypes) {

                    $scope.issueTypes = issueTypes;

                    console.log(issueTypes[0]);

                });
            };
            $scope.loadIssueTypes();

            //TODO post function
            $scope.saveIssue = function () {
                console.log("ok");
                $http.post(apiUrl + '/issues').success(function (issue) {

                    console.log(issue);

                });
            };

            $scope.takePicture = function (CameraService) {
                CameraService.getPicture({
                    quality: 75,
                    targetWidth: 400,
                    targetHeight: 300,
                    destinationType: Camera.DestinationType.DATA_URL
                }).then(function (imageData) {
                    $scope.imageData = imageData;
                });
            };

        }
    )

    .factory('IssueService', function ($http, apiUrl) {
        var service = {

            findIssuesByUser: function () {

                return $http.get(apiUrl + '/me/issues',
                    {headers: {'x-sort': 'createdOn'}}).success(function (issues) {
                    console.log(issues);
                    return issues; //appel rend une promesse qui sera résolu avec les issues quand le résultat sera arrivé --> .then possible

                });

            }
        };

        return service;
    })

    .factory("CameraService", function ($q) {
        return {
            getPicture: function (options) {
                var deferred = $q.defer();
                navigator.camera.getPicture(function (result) {
                    // do any magic you need
                    deferred.resolve(result);
                }, function (err) {
                    deferred.reject(err);
                }, options);
                return deferred.promise;
            }
        }
    })

    .controller('HomeController',
        function ($scope, $http, apiUrl, IssueService) {

            $scope.loadIssuesByUser = function () {
                console.log("ok my issues");

                IssueService.findIssuesByUser().then(function (issues) {
                    $scope.issues = issues;
                    console.log(issues);
                });

            }
            $scope.loadIssuesByUser();

        })

    .controller('ProfileController',
        function ($scope, $http, apiUrl, AuthService, IssueService) {

            $scope.loadUserInfo = function () {
                console.log("ok my issues");

                userId = AuthService.currentUserId;
                $http.get(apiUrl + '/users/' + userId).success(function (user) {

                    $scope.user = user;

                    console.log(user);

                });
            }
            $scope.loadUserInfo();

            $scope.countIssues = function () {
                IssueService.findIssuesByUser().then(function (issues) {
                    $scope.issues = issues;
                    $scope.nbIssues = issues.data.length;

                });
            };
            $scope.countIssues();

        })

    .controller("CameraTestController", function (CameraService) {

    })

    .controller("UserIssuesController", function (CameraService) {

    });
;

;

/*
 TODO
 {
 "description": "Integer at metus vitae erat porta pellentesque.",
 "lng": "6.651479812689227",
 "lat": "46.77227088657382",
 "imageUrl": "http://www.somewhere.localhost.localdomain",
 "issueTypeId": "54d8ae183fd30364605c81b1"
 }*/
<commit_msg>pour un pull<commit_after>angular.module('citizen-engagement.newIssueCtrl', [])

    .factory("CameraService", function ($q) {
        return {
            getPicture: function (options) {
                var deferred = $q.defer();

                navigator.camera.getPicture(function (result) {
                    // do any magic you need
                    deferred.resolve(result);
                }, function (err) {
                    deferred.reject(err);
                }, options);

                return deferred.promise;
            }
        }
    })
    .controller('NewIssueCtrl',
        function ($scope, $http, apiUrl) {
            $scope.loadIssueTypes = function () {
                console.log("ok");
                $http.get(apiUrl + '/issueTypes').success(function (issueTypes) {

                    $scope.issueTypes = issueTypes;

                    console.log(issueTypes[0]);

                });
            };
            $scope.loadIssueTypes();

            //TODO post function
            $scope.saveIssue = function () {
                console.log("ok");
                $http.post(apiUrl + '/issues').success(function (issue) {

                    console.log(issue);

                });
            };

            $scope.takePicture = function (CameraService) {
                CameraService.getPicture({
                    quality: 75,
                    targetWidth: 400,
                    targetHeight: 300,
                    destinationType: Camera.DestinationType.DATA_URL
                }).then(function (imageData) {
                    $scope.imageData = imageData;
                });
            };

        }
    )

    .factory('IssueService', function ($http, apiUrl) {
        var service = {

            findIssuesByUser: function () {

                return $http.get(apiUrl + '/me/issues',
                    {headers: {'x-sort': 'createdOn'}}).success(function (issues) {
                    console.log(issues);
                    return issues; //appel rend une promesse qui sera résolu avec les issues quand le résultat sera arrivé --> .then possible

                });

            }
        };

        return service;
    })

    .factory("CameraService", function ($q) {
        return {
            getPicture: function (options) {
                var deferred = $q.defer();
                navigator.camera.getPicture(function (result) {
                    // do any magic you need
                    deferred.resolve(result);
                }, function (err) {
                    deferred.reject(err);
                }, options);
                return deferred.promise;
            }
        }
    })

    .controller('HomeController',
        function ($scope, $http, apiUrl, IssueService) {

            $scope.loadIssuesByUser = function () {
                console.log("ok my issues");

                IssueService.findIssuesByUser().then(function (issues) {
                    $scope.issues = issues;
                    console.log(issues);
                });

            }
            $scope.loadIssuesByUser();

        })

    .controller('ProfileController',
        function ($scope, $http, apiUrl, AuthService, IssueService) {

            $scope.loadUserInfo = function () {
                console.log("ok my issues");

                userId = AuthService.currentUserId;
                $http.get(apiUrl + '/users/' + userId).success(function (user) {

                    $scope.user = user;

                    console.log(user);

                });
            }
            $scope.loadUserInfo();

            $scope.countIssues = function () {
                IssueService.findIssuesByUser().then(function (issues) {
                    $scope.issues = issues;
                    $scope.nbIssues = issues.data.length;

                });
            };
            $scope.countIssues();

        })

    .controller("CameraTestController", function (CameraService) {

    })

    .controller("UserIssuesController", function (CameraService) {

    });

/*
 TODO
 {
 "description": "Integer at metus vitae erat porta pellentesque.",
 "lng": "6.651479812689227",
 "lat": "46.77227088657382",
 "imageUrl": "http://www.somewhere.localhost.localdomain",
 "issueTypeId": "54d8ae183fd30364605c81b1"
 }*/
<|endoftext|>


<commit_before>pp.views.QuestBig = Backbone.View.extend({
    template: _.template($('#template-quest-big').text()),

    events: {
        "click .quest-close": "close",
        "click .quest-reopen": "reopen",
        "click .quest-like": "like",
        "click .quest-unlike": "unlike",
        "click .quest-delete": "destroy",
        "dblclick .quest-title": "edit",
        "keypress .quest-edit": "updateOnEnter",
        "blur .quest-edit": "closeEdit"
    },

    close: function () {
        this.model.close();
    },

    reopen: function () {
        this.model.reopen();
    },

    like: function () {
        this.model.like();
    },

    unlike: function () {
        this.model.unlike();
    },

    edit: function () {
        if (!this.isOwned()) {
            return;
        }
        this.$('.quest-edit').show();
        this.$('.quest-title').hide();
        this.$('.quest-edit').focus();
    },

    updateOnEnter: function (e) {
        if (e.keyCode == 13) this.closeEdit();
    },

    closeEdit: function() {
        var value = this.$('.quest-edit').val();
        if (!value) {
            return;
        }
        this.model.save({ name: value });
        this.$('.quest-edit').hide();
        this.$('.quest-title').show();
    },

    destroy: function () {
        var that = this;
        bootbox.confirm("Quest and all comments will be destroyed permanently. Are you sure?", function(result) {
            if (result) {
                that.model.destroy({
                    success: function(model, response) {
                                 pp.app.router.navigate("/", { trigger: true });
                             },
                    error: pp.app.onError
                });
            }
        });
    },

    initialize: function () {
        // FIXME - fix double rendering
        this.model.bind('change', this.render, this);
        pp.app.user.bind('change', this.render, this);

        this.model.fetch();
    },

    isOwned: function () {
        return (pp.app.user.get('login') == this.model.get('user'));
    },

    render: function () {
        var params = this.model.toJSON();
        // TODO - should we move this to model?
        params.currentUser = pp.app.user.get('login');
        params.my = this.isOwned();
        if (!params.likes) {
            params.likes = [];
        }
        this.$el.html(this.template(params));
        this.$el.find('[data-toggle=tooltip]').tooltip();
    }
});
<commit_msg>refactor views/quest/big to use View.Common<commit_after>pp.views.QuestBig = pp.View.Common.extend({
    t: 'quest-big',

    events: {
        "click .quest-close": "close",
        "click .quest-reopen": "reopen",
        "click .quest-like": "like",
        "click .quest-unlike": "unlike",
        "click .quest-delete": "destroy",
        "dblclick .quest-title": "edit",
        "keypress .quest-edit": "updateOnEnter",
        "blur .quest-edit": "closeEdit"
    },

    close: function () {
        this.model.close();
    },

    reopen: function () {
        this.model.reopen();
    },

    like: function () {
        this.model.like();
    },

    unlike: function () {
        this.model.unlike();
    },

    edit: function () {
        if (!this.isOwned()) {
            return;
        }
        this.$('.quest-edit').show();
        this.$('.quest-title').hide();
        this.$('.quest-edit').focus();
    },

    updateOnEnter: function (e) {
        if (e.keyCode == 13) this.closeEdit();
    },

    closeEdit: function() {
        var value = this.$('.quest-edit').val();
        if (!value) {
            return;
        }
        this.model.save({ name: value });
        this.$('.quest-edit').hide();
        this.$('.quest-title').show();
    },

    destroy: function () {
        var that = this;
        bootbox.confirm("Quest and all comments will be destroyed permanently. Are you sure?", function(result) {
            if (result) {
                that.model.destroy({
                    success: function(model, response) {
                                 pp.app.router.navigate("/", { trigger: true });
                             },
                    error: pp.app.onError
                });
            }
        });
    },

    initialize: function () {
        pp.View.Common.prototype.initialize.apply(this, arguments);

        // FIXME - fix double rendering
        this.model.bind('change', this.render, this);
        pp.app.user.bind('change', this.render, this);

        this.model.fetch();
    },

    isOwned: function () {
        return (pp.app.user.get('login') == this.model.get('user'));
    },

    serialize: function () {
        var params = this.model.toJSON();
        // TODO - should we move this to model?
        params.currentUser = pp.app.user.get('login');
        params.my = this.isOwned();
        if (!params.likes) {
            params.likes = [];
        }
        return params;
    },

    features: ['tooltip'],
});
<|endoftext|>


<commit_before>import { bindable, customElement } from 'aurelia-templating';
import { DOM } from 'aurelia-pal';
import { bindingMode } from 'aurelia-binding';
import { inject } from 'aurelia-dependency-injection';
import { getBooleanFromAttributeValue } from '../common/attributes';

@customElement('md-switch')
@inject(Element)
export class MdSwitch {
  @bindable({
    defaultBindingMode: bindingMode.twoWay
  }) mdChecked;
  @bindable() mdDisabled;
  @bindable() mdLabelOff = 'Off';
  @bindable() mdLabelOn = 'On';

  constructor(element) {
    this.element = element;
    this.handleChange = this.handleChange.bind(this);
  }

  attached() {
    this.checkbox.checked = getBooleanFromAttributeValue(this.mdChecked);
    if (getBooleanFromAttributeValue(this.mdDisabled)) {
      this.checkbox.disabled = true;
    }
    this.checkbox.addEventListener('change', this.handleChange);
  }

  detached() {
    this.checkbox.removeEventListener('change', this.handleChange);
  }

  handleChange() {
    this.mdChecked = this.checkbox.checked;
  }

  blur() {
    const event = DOM.createCustomEvent('blur');
    this.element.dispatchEvent(event);
  }

  mdCheckedChanged(newValue) {
    if (this.checkbox) {
      this.checkbox.checked = !!newValue;
    }
  }
}
<commit_msg>chore(md-switch): remove aurelia-pal dependency<commit_after>import { bindable, customElement } from 'aurelia-templating';
import { bindingMode } from 'aurelia-binding';
import { inject } from 'aurelia-dependency-injection';
import { getBooleanFromAttributeValue } from '../common/attributes';
import {fireEvent} from '../common/events';

@customElement('md-switch')
@inject(Element)
export class MdSwitch {
  @bindable({
    defaultBindingMode: bindingMode.twoWay
  }) mdChecked;
  @bindable() mdDisabled;
  @bindable() mdLabelOff = 'Off';
  @bindable() mdLabelOn = 'On';

  constructor(element) {
    this.element = element;
    this.handleChange = this.handleChange.bind(this);
  }

  attached() {
    this.checkbox.checked = getBooleanFromAttributeValue(this.mdChecked);
    if (getBooleanFromAttributeValue(this.mdDisabled)) {
      this.checkbox.disabled = true;
    }
    this.checkbox.addEventListener('change', this.handleChange);
  }

  detached() {
    this.checkbox.removeEventListener('change', this.handleChange);
  }

  handleChange() {
    this.mdChecked = this.checkbox.checked;
  }

  blur() {
    fireEvent(this.element, 'blur');
  }

  mdCheckedChanged(newValue) {
    if (this.checkbox) {
      this.checkbox.checked = !!newValue;
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

import IJ from '../ij';

export default function split({preserveAlpha = true} = {}) {

    this.checkProcessable('split', {
        bitDepth: [8,16]
    });

    // split will always return an array of images
    var images=[];

    if (this.components === 1) {
        return images.push(this.clone());
    }

    var data = this.data;
    if (this.alpha && preserveAlpha) {
        for (let i=0; i<this.components; i++) {
            var newImage = IJ.createFrom(this, {
                kind: {
                    components: 1,
                    alpha: true,
                    bitDepth: this.bitDepth,
                    colorModel: null
                }
            });
            let ptr=0;
            for (let j=0; j<data.length; j+=this.channels) {
                newImage.data[ptr++] = data[j + i];
                newImage.data[ptr++]=data[j + this.components];
            }
            images.push(newImage);
        }
    } else {
        for (let i=0; i<this.channels; i++) {
            var newImage = IJ.createFrom(this, {
                kind: {
                    components: 1,
                    alpha: false,
                    bitDepth: this.bitDepth,
                    colorModel: null
                }
            });
            let ptr=0;
            for (let j=0; j<data.length; j+=this.channels) {
                newImage.data[ptr++] = data[j + i];
            }
            images.push(newImage);
        }
    }

    return images;
}
<commit_msg>split: correctly return an array if one component<commit_after>'use strict';

import IJ from '../ij';

export default function split({preserveAlpha = true} = {}) {

    this.checkProcessable('split', {
        bitDepth: [8, 16]
    });

    // split will always return an array of images
    if (this.components === 1) {
        return [this.clone()];
    }

    let images = [];

    let data = this.data;
    if (this.alpha && preserveAlpha) {
        for (let i = 0; i < this.components; i++) {
            let newImage = IJ.createFrom(this, {
                kind: {
                    components: 1,
                    alpha: true,
                    bitDepth: this.bitDepth,
                    colorModel: null
                }
            });
            let ptr = 0;
            for (let j = 0; j < data.length; j += this.channels) {
                newImage.data[ptr++] = data[j + i];
                newImage.data[ptr++] = data[j + this.components];
            }
            images.push(newImage);
        }
    } else {
        for (let i = 0; i < this.channels; i++) {
            let newImage = IJ.createFrom(this, {
                kind: {
                    components: 1,
                    alpha: false,
                    bitDepth: this.bitDepth,
                    colorModel: null
                }
            });
            let ptr = 0;
            for (let j = 0; j < data.length; j += this.channels) {
                newImage.data[ptr++] = data[j + i];
            }
            images.push(newImage);
        }
    }

    return images;
}
<|endoftext|>


<commit_before>export default {
  en: { // English
    months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
    monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
    weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
    weekdaysMin: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
  },
  zh: { // Chinese
    months: ['一 月', '二 月', '三 月', '四 月', '五 月', '六 月', '七 月', '八 月', '九 月', '十 月', '十 一 月', '十 二 月'],
    monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
    weekdays: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
    weekdaysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
    weekdaysMin: ['日', '一', '二', '三', '四', '五', '六'],
  },
  ru: { // Russian
    months: ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'],
    monthsShort: ['янв', 'февр', 'мар', 'апр', 'мая', 'июня', 'июля', 'авг', 'сент', 'окт', 'нояб', 'дек'],
    weekdays: ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'],
    weekdaysShort: ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'],
    weekdaysMin: ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'],
  },
  fr: { // French
    months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
    monthsShort: ['Janv', 'Févr', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'août', 'Sept', 'Oct', 'Nov', 'Déc'],
    weekdays: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'vendredi', 'Samedi'],
    weekdaysShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
    weekdaysMin: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],
  },
  es: { // Spanish
    months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
    monthsShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
    weekdays: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
    weekdaysShort: ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'],
    weekdaysMin: ['D', 'L', 'M', 'X', 'J', 'V', 'S'],
  },
  de: { // German
    months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
    monthsShort: ['Jan', 'Feb', 'März', 'Apr', 'Mai', 'Juni', 'Juli', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
    weekdays: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
    weekdaysShort: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa.'],
    weekdaysMin: ['S', 'M', 'D', 'M', 'D', 'F', 'S'],
  },
  pt: { // Portuguese
    months: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'],
    monthsShort: ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'],
    weekdays: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
    weekdaysShort: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
    weekdaysMin: ['Do', '2ª', '3ª', '4ª', '5ª', '6ª', 'Sá'],
  },
  it: { // Italian
    months: ['gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'],
    monthsShort: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
    weekdays: ['domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato'],
    weekdaysShort: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
    weekdaysMin: ['do', 'lu', 'ma', 'me', 'gi', 've', 'sa'],
  },
  ja: { // Japanese
    months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
    monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
    weekdays: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'],
    weekdaysShort: ['日', '月', '火', '水', '木', '金', '土'],
    weekdaysMin: ['日', '月', '火', '水', '木', '金', '土'],
  },
  tr: { // Turkish
    months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'],
    monthsShort: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
    weekdays: ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'],
    weekdaysShort: ['Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Paz'],
    weekdaysMin: ['P', 'S', 'Ç', 'P', 'C', 'C', 'P'],
  },
  sv: { // Swedish
    months: ['Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December'],
    monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
    weekdays: ['Söndag', 'Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lördag'],
    weekdaysShort: ['Sun', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör'],
    weekdaysMin: ['S', 'M', 'T', 'O', 'T', 'F', 'L'],
  },
};
<commit_msg>Added Finnish translation to locales<commit_after>export default {
  en: { // English
    months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
    monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
    weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    weekdaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
    weekdaysMin: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
  },
  zh: { // Chinese
    months: ['一 月', '二 月', '三 月', '四 月', '五 月', '六 月', '七 月', '八 月', '九 月', '十 月', '十 一 月', '十 二 月'],
    monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
    weekdays: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
    weekdaysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
    weekdaysMin: ['日', '一', '二', '三', '四', '五', '六'],
  },
  ru: { // Russian
    months: ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'],
    monthsShort: ['янв', 'февр', 'мар', 'апр', 'мая', 'июня', 'июля', 'авг', 'сент', 'окт', 'нояб', 'дек'],
    weekdays: ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'],
    weekdaysShort: ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'],
    weekdaysMin: ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'],
  },
  fr: { // French
    months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
    monthsShort: ['Janv', 'Févr', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'août', 'Sept', 'Oct', 'Nov', 'Déc'],
    weekdays: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'vendredi', 'Samedi'],
    weekdaysShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
    weekdaysMin: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],
  },
  es: { // Spanish
    months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
    monthsShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
    weekdays: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
    weekdaysShort: ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'],
    weekdaysMin: ['D', 'L', 'M', 'X', 'J', 'V', 'S'],
  },
  de: { // German
    months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
    monthsShort: ['Jan', 'Feb', 'März', 'Apr', 'Mai', 'Juni', 'Juli', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
    weekdays: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
    weekdaysShort: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa.'],
    weekdaysMin: ['S', 'M', 'D', 'M', 'D', 'F', 'S'],
  },
  pt: { // Portuguese
    months: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'],
    monthsShort: ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'],
    weekdays: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
    weekdaysShort: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
    weekdaysMin: ['Do', '2ª', '3ª', '4ª', '5ª', '6ª', 'Sá'],
  },
  it: { // Italian
    months: ['gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'],
    monthsShort: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
    weekdays: ['domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato'],
    weekdaysShort: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
    weekdaysMin: ['do', 'lu', 'ma', 'me', 'gi', 've', 'sa'],
  },
  ja: { // Japanese
    months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
    monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
    weekdays: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'],
    weekdaysShort: ['日', '月', '火', '水', '木', '金', '土'],
    weekdaysMin: ['日', '月', '火', '水', '木', '金', '土'],
  },
  tr: { // Turkish
    months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'],
    monthsShort: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
    weekdays: ['Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'],
    weekdaysShort: ['Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Paz'],
    weekdaysMin: ['P', 'S', 'Ç', 'P', 'C', 'C', 'P'],
  },
  sv: { // Swedish
    months: ['Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December'],
    monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
    weekdays: ['Söndag', 'Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lördag'],
    weekdaysShort: ['Sun', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör'],
    weekdaysMin: ['S', 'M', 'T', 'O', 'T', 'F', 'L'],
  },
  fi: { // Finnish
    months: ['Tammikuu', 'Helmikuu', 'Maaliskuu', 'Huhtikuu', 'Toukokuu', 'Kesäkuu', 'Heinäkuu', 'Elokuu', 'Syyskuu', 'Lokakuu', 'Marraskuu', 'Joulukuu'],
    monthsShort: ['Tammi', 'Helmi', 'Maalis', 'Huhti', 'Touko', 'Kesä', 'Heinä', 'Elo', 'Syys', 'Loka', 'Marras', 'Joulu'],
    weekdays: ['Sunnuntai', 'Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai'],
    weekdaysShort: ['Su', 'Ma', 'Ti', 'Ke', 'To', 'Pe', 'La'],
    weekdaysMin: ['S', 'M', 'T', 'K', 'T', 'P', 'L'],
  }
};
<|endoftext|>