<commit_before>var userid = null;

angular.module('ionicApp', ['ionic'])

.config(function($stateProvider, $urlRouterProvider) {

  $stateProvider
    .state('signin', {
      url: '/sign-in',
      templateUrl: 'templates/sign-in.html',
      controller: 'SignInCtrl'
    })
    .state('schedule', {
      url: '/schedule',
      templateUrl: 'templates/schedule.html',
      controller: 'ScheduleCtrl'
    })
    .state('form', {
      url: '/form',
      templateUrl: 'templates/app-form.html',
      controller: 'FormCtrl'
    })
    .state('signup', {
      url: '/sign-up',
      templateUrl: 'templates/sign-up.html',
      controller: 'SignUpCtrl'
    });
  $urlRouterProvider.otherwise('/sign-in');
})

.controller('ScheduleCtrl', function($scope, $http, $ionicPopup){
  $scope.stories = [];
  $scope.appointments = info;

  $scope.showAlert = function(title, body) {
   var alertPopup = $ionicPopup.alert({
     title: title,
     template: body
   });
   alertPopup.then(function(res) {
     console.log('Error');
   });
 };

  $scope.doRefresh = function() {
    console.log('called refresh!');
    $http.get('#/schedule')
     .success(function(/*newItems*/) {
      //  $scope.items = newItems;
      $scope.appointments = info;
     })
     .finally(function() {
       // Stop the ion-refresher from spinning
       $scope.$broadcast('scroll.refreshComplete');
     });
    };

  $scope.removeApp = function(id){
    //Works! Logs correct id :D
    console.log(id);
    $.ajax({
      //removed the /2 from url.
      url: ('https://api-us.clusterpoint.com//100600/Appointly/' + id),
      type: 'DELETE',
      dataType: 'json',
      // data: ("{'id'=" + id + "}"),
      beforeSend: function(xhr) {
        xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
      },
      success: function(data) {
        if (typeof success != 'undefined') {
          success(data);
        }
        $scope.showAlert("Success!", "Appointment has been canceled!");
      },
        fail: function(data) {
          alert(data.error);
          console.log('Fail!');
          if (typeof fail != 'undefined') {
            fail(data);
          }
          $scope.showAlert("Error!", "Appointment could not be deleted!");
        }
      })

      setTimeout(function()
      {
          $scope.doRefresh();

      }, 2000);
  }

})

.controller('SignUpCtrl', function($http, $scope, $state, $ionicPopup) {
  $scope.showAlert = function(title, body) {
    var alertPopup = $ionicPopup.alert({
      title: title,
      template: body
    });
    alertPopup.then(function(res) {
      $state.go('signin');
    });
  };

  $scope.signup = function(name, email, phone, pass) {

    var send = {
        // "name"      : name,
        // "email"     : email,
        // "phone"     : phone,
        // "pass"      : pass,
        "id"            : phone,
        "password"      : pass,
    }

    console.log(JSON.stringify(send));

    $.ajax({
      url: 'https://api-us.clusterpoint.com/100600/User_Accounts/_insert',
      type: 'POST',
      dataType: 'json',
      data: JSON.stringify(send),
      beforeSend: function(xhr) {
        xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
      },
      success: function(data) {
        if (typeof success != 'undefined') {
          success(data);
        }
        console.log(data);
        $scope.showAlert('Done!','You are in!');
      },
      fail: function(data) {
        alert('No!');
        alert(data.error);
        console.log('Fail!');
        if (typeof fail != 'undefined') {
          fail(data);
        }
      }
    })

    // users.done(function (data){
    //   var d = data.documents;
    //   //----------------------------------------------------------
    //   console.log(d);
    //   var result = $.grep(d, function(e) {
    //     return e.id == number && e.password == password;
    //   });
    //   console.log(result);
    //   if (result.length == 0) {
    //     $scope.showAlert('Error', 'Incorrect Number or Password!');
    //   } else if (result.length == 1) {
    //     $state.go('sign-in');
    //   } else {
    //     $scope.showAlert('Error', 'Duplicate Users Found!');
    //   }
    // })
  }

})

.controller('SignInCtrl', function($http, $scope, $state, $ionicPopup) {

  $scope.showAlert = function(title, body) {
    var alertPopup = $ionicPopup.alert({
      title: title,
      template: body
    });
    alertPopup.then(function(res) {
      console.log('Error');
    });
  };

  console.log('Enter SingInCtrl');
  $scope.url = '#';
  $scope.acc_number = '';
  $scope.acc_password = '';
  $scope.login = function(number, password) {
    console.log('Enter login function');
    console.log('number: ' + number);
    console.log('password: ' + password);
    var users = $.ajax({
      url: 'https://api-us.clusterpoint.com/100600/User_Accounts/_list_last',
      type: 'GET',
      dataType: 'json',
      // data      : '{"query": "<name>Test</name>"}',
      beforeSend: function(xhr) {
        xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
      },
      success: function(data) {
        console.log('Retrived Users!');
        if (typeof success != 'undefined') {
          // jQuery.parseJSON(doc.responseJSON.documents.toSource();
          success(data);
        }
      },
      fail: function(data) {
        alert(data.error);
        console.log('Fail!');
        if (typeof fail != 'undefined') {
          fail(data);
        }
      }
    });

    users.done(function(data) {
      var d = data.documents;
      //----------------------------------------------------------
      console.log(d);
      var result = $.grep(d, function(e) {
        return e.id == number && e.password == password;
      });
      console.log(result);
      if (result.length == 0) {
        $scope.showAlert('Error', 'Incorrect Number or Password!');
      } else if (result.length == 1) {
        userid = number;

        $state.go('schedule');
      } else {
        $scope.showAlert('Error', 'Duplicate Users Found!');
      }
    });

  }

  // $scope.signIn = function(user) {
  //   console.log('Sign-In', user);
  //   $state.go('schedule');
  // };

})

.controller('HomeTabCtrl', function($scope) {
  console.log('HomeTabCtrl');
})

.controller('FormCtrl', function($http, $scope, $state, $timeout, $ionicPopup, $filter) {

    $scope.app_numb;
    $scope.app_name;
    $scope.app_img_p = 'multicare.png';
    // ['multicare.png','hb.png','health.png','dental.png'];
    $scope.app_date = new Date();
    $scope.app_time = $scope.app_date;

    $scope.doRefresh = function() {
      console.log('called refresh!');
      $http.get('#/schedule')
       .success(function(/*newItems*/) {
        //  $scope.items = newItems;
        $scope.appointments = info;
       })
       .finally(function() {
         // Stop the ion-refresher from spinning
         $scope.$broadcast('scroll.refreshComplete');
       });
      };

    $scope.go = function(app_numb, app_date, app_name, app_time, app_location) {
      // app_date = app_date.toString();
      // app_time = app_time.toString();
      // console.log('Passed into function:');
      // console.log(app_id);
      // console.log(app_date);
      // console.log(app_name);
      // console.log(app_time);
      // var fullTime = app_time.split(" ");
      // app_time = fullTime[0];
      // var app_zone = fullTime[1];
      // app_date(new Date(), "mmmm dS, yyyy");

      // console.log('Fulltime: ');
      // console.log(fullTime);
      // console.log('Time: ');
      // console.log(app_time);
      // console.log('Zone: ');
      // console.log(app_zone);

      $scope.app_id = userid + Date.now().toString();

      var send = {
        "id"      :   app_id,
        "numb"    :   app_numb,
        "name"    :   app_name,
        "img_p"   :   $scope.app_img_p,
        "date"    :   app_date.toString(),
        "time"    :   app_time.toString(),
        "location":   app_location
      }

      console.log(send);

      $scope.showAlert = function() {
        var alertPopup = $ionicPopup.alert({
          title: 'All done!',
          template: 'Appointment Requested'
        });
        alertPopup.then(function(res) {
          $state.go('schedule');
          console.log('Thank you for not eating my delicious ice cream cone');
          setTimeout(function()
          {
              $scope.doRefresh();

          }, 2000);
        });
      };

    var test_date = $filter('date')(app_date, "dd/MM/yyyy");
    var test_time = $filter('date')(app_time, "HH:mm a");

      // Simple GET request example :
    $http.get('http://appointly.mybluemix.net/twiliouth?number=+1' + app_numb + '&message=An appointment has been requested on ' + test_date + ' at ' + test_time + '.').
      success(function(data, status, headers, config) {
        // this callback will be called asynchronously
        // when the response is available
        console.log('MSG sent to phone!');
      }).
      error(function(data, status, headers, config) {
        // called asynchronously if an error occurs
        // or server returns response with an error status.
      });

      // $.ajax({
      //   url: 'http://appointly.mybluemix.net/',
      //   type: 'GET',
      //   contentType: "text/plain; charset=utf-8",
      //   dataType: 'text',
      //   data: send.name + " scheduled an appoinment with you at " + send.time + " on " + send.date + ", location: " + send.location,
      //   // beforeSend: function(xhr) {
      //   //   xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
      //   // },
      //   success: function(data) {
      //     alert("Submitted!");
      //     if (typeof success != 'undefined') {
      //       // jQuery.parseJSON(doc.responseJSON.documents.toSource());
      //       success(data);
      //     }
      //   },
      //   fail: function(data) {
      //     alert('No!');
      //     alert(data.error);
      //     console.log('Fail!');
      //     if (typeof fail != 'undefined') {
      //       fail(data);
      //     }
      //   }
      // })

      $.ajax({
        //removed the /2 from url.
        url: 'https://api-us.clusterpoint.com//100600/Appointly/_insert',
        type: 'POST',
        dataType: 'json',
        data: JSON.stringify(send),
        beforeSend: function(xhr) {
          xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
        },
        success: function(data) {
          if (typeof success != 'undefined') {
            success(data);
          }
          $scope.showAlert();
        },
        fail: function(data) {
          alert('No!');
          alert(data.error);
          console.log('Fail!');
          if (typeof fail != 'undefined') {
            fail(data);
          }
        }
      })

    }
  })
  .controller('MainCtrl', function($http, $scope) {
    $scope.test = 'Scope Works!';

    // $scope.stories = [];
    //
    // $scope.appointments = info;

    // [{
    //   name: 'Green Valley Clinic',
    //   img_p: 'health.png',
    //   date: 'June 30th',
    //   time: 10,
    //   zone: 'am'
    // }, {
    //   name: 'Silver Point Dental',
    //   img_p: 'dental.png',
    //   date: 'July 16th',
    //   time: 2,
    //   zone: 'pm'
    // }, {
    //   name: 'Multicare',
    //   img_p: 'multicare.png',
    //   date: 'July 25th',
    //   time: 4,
    //   zone: 'pm'
    // }];

  });
<commit_msg>Fucked up mistakes<commit_after>var userid = null;

angular.module('ionicApp', ['ionic'])

.config(function($stateProvider, $urlRouterProvider) {

  $stateProvider
    .state('signin', {
      url: '/sign-in',
      templateUrl: 'templates/sign-in.html',
      controller: 'SignInCtrl'
    })
    .state('schedule', {
      url: '/schedule',
      templateUrl: 'templates/schedule.html',
      controller: 'ScheduleCtrl'
    })
    .state('form', {
      url: '/form',
      templateUrl: 'templates/app-form.html',
      controller: 'FormCtrl'
    })
    .state('signup', {
      url: '/sign-up',
      templateUrl: 'templates/sign-up.html',
      controller: 'SignUpCtrl'
    });
  $urlRouterProvider.otherwise('/sign-in');
})

.controller('ScheduleCtrl', function($scope, $http, $ionicPopup){
  $scope.stories = [];
  $scope.appointments = info;

  $scope.showAlert = function(title, body) {
   var alertPopup = $ionicPopup.alert({
     title: title,
     template: body
   });
   alertPopup.then(function(res) {
     console.log('Error');
   });
 };

  $scope.doRefresh = function() {
    console.log('called refresh!');
    $http.get('#/schedule')
     .success(function(/*newItems*/) {
      //  $scope.items = newItems;
      $scope.appointments = info;
     })
     .finally(function() {
       // Stop the ion-refresher from spinning
       $scope.$broadcast('scroll.refreshComplete');
     });
    };

  $scope.removeApp = function(id){
    //Works! Logs correct id :D
    console.log(id);
    $.ajax({
      //removed the /2 from url.
      url: ('https://api-us.clusterpoint.com//100600/Appointly/' + id),
      type: 'DELETE',
      dataType: 'json',
      // data: ("{'id'=" + id + "}"),
      beforeSend: function(xhr) {
        xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
      },
      success: function(data) {
        if (typeof success != 'undefined') {
          success(data);
        }
        $scope.showAlert("Success!", "Appointment has been canceled!");
      },
        fail: function(data) {
          alert(data.error);
          console.log('Fail!');
          if (typeof fail != 'undefined') {
            fail(data);
          }
          $scope.showAlert("Error!", "Appointment could not be deleted!");
        }
      })

      setTimeout(function()
      {
          $scope.doRefresh();

      }, 2000);
  }

})

.controller('SignUpCtrl', function($http, $scope, $state, $ionicPopup) {
  $scope.showAlert = function(title, body) {
    var alertPopup = $ionicPopup.alert({
      title: title,
      template: body
    });
    alertPopup.then(function(res) {
      $state.go('signin');
    });
  };

  $scope.signup = function(name, email, phone, pass) {

    var send = {
        // "name"      : name,
        // "email"     : email,
        // "phone"     : phone,
        // "pass"      : pass,
        "id"            : phone,
        "password"      : pass,
    }

    console.log(JSON.stringify(send));

    $.ajax({
      url: 'https://api-us.clusterpoint.com/100600/User_Accounts/_insert',
      type: 'POST',
      dataType: 'json',
      data: JSON.stringify(send),
      beforeSend: function(xhr) {
        xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
      },
      success: function(data) {
        if (typeof success != 'undefined') {
          success(data);
        }
        console.log(data);
        $scope.showAlert('Done!','You are in!');
      },
      fail: function(data) {
        alert('No!');
        alert(data.error);
        console.log('Fail!');
        if (typeof fail != 'undefined') {
          fail(data);
        }
      }
    })

    // users.done(function (data){
    //   var d = data.documents;
    //   //----------------------------------------------------------
    //   console.log(d);
    //   var result = $.grep(d, function(e) {
    //     return e.id == number && e.password == password;
    //   });
    //   console.log(result);
    //   if (result.length == 0) {
    //     $scope.showAlert('Error', 'Incorrect Number or Password!');
    //   } else if (result.length == 1) {
    //     $state.go('sign-in');
    //   } else {
    //     $scope.showAlert('Error', 'Duplicate Users Found!');
    //   }
    // })
  }

})

.controller('SignInCtrl', function($http, $scope, $state, $ionicPopup) {

  $scope.showAlert = function(title, body) {
    var alertPopup = $ionicPopup.alert({
      title: title,
      template: body
    });
    alertPopup.then(function(res) {
      console.log('Error');
    });
  };

  console.log('Enter SingInCtrl');
  $scope.url = '#';
  $scope.acc_number = '';
  $scope.acc_password = '';
  $scope.login = function(number, password) {
    console.log('Enter login function');
    console.log('number: ' + number);
    console.log('password: ' + password);
    var users = $.ajax({
      url: 'https://api-us.clusterpoint.com/100600/User_Accounts/_list_last',
      type: 'GET',
      dataType: 'json',
      // data      : '{"query": "<name>Test</name>"}',
      beforeSend: function(xhr) {
        xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
      },
      success: function(data) {
        console.log('Retrived Users!');
        if (typeof success != 'undefined') {
          // jQuery.parseJSON(doc.responseJSON.documents.toSource();
          success(data);
        }
      },
      fail: function(data) {
        alert(data.error);
        console.log('Fail!');
        if (typeof fail != 'undefined') {
          fail(data);
        }
      }
    });

    users.done(function(data) {
      var d = data.documents;
      //----------------------------------------------------------
      console.log(d);
      var result = $.grep(d, function(e) {
        return e.id == number && e.password == password;
      });
      console.log(result);
      if (result.length == 0) {
        $scope.showAlert('Error', 'Incorrect Number or Password!');
      } else if (result.length == 1) {
        userid = number;

        $state.go('schedule');
      } else {
        $scope.showAlert('Error', 'Duplicate Users Found!');
      }
    });

  }

  // $scope.signIn = function(user) {
  //   console.log('Sign-In', user);
  //   $state.go('schedule');
  // };

})

.controller('HomeTabCtrl', function($scope) {
  console.log('HomeTabCtrl');
})

.controller('FormCtrl', function($http, $scope, $state, $timeout, $ionicPopup, $filter) {

    $scope.app_numb;
    $scope.app_name;
    $scope.app_img_p = 'multicare.png';
    // ['multicare.png','hb.png','health.png','dental.png'];
    $scope.app_date = new Date();
    $scope.app_time = $scope.app_date;

    $scope.doRefresh = function() {
      console.log('called refresh!');
      $http.get('#/schedule')
       .success(function(/*newItems*/) {
        //  $scope.items = newItems;
        $scope.appointments = info;
       })
       .finally(function() {
         // Stop the ion-refresher from spinning
         $scope.$broadcast('scroll.refreshComplete');
       });
      };

    $scope.go = function(app_numb, app_date, app_name, app_time, app_location) {
      // app_date = app_date.toString();
      // app_time = app_time.toString();
      // console.log('Passed into function:');
      // console.log(app_id);
      // console.log(app_date);
      // console.log(app_name);
      // console.log(app_time);
      // var fullTime = app_time.split(" ");
      // app_time = fullTime[0];
      // var app_zone = fullTime[1];
      // app_date(new Date(), "mmmm dS, yyyy");

      // console.log('Fulltime: ');
      // console.log(fullTime);
      // console.log('Time: ');
      // console.log(app_time);
      // console.log('Zone: ');
      // console.log(app_zone);

      var app_id = userid + Date.now().toString();

      var send = {
        "id"      :   app_id,
        "numb"    :   app_numb,
        "name"    :   app_name,
        "img_p"   :   $scope.app_img_p,
        "date"    :   app_date.toString(),
        "time"    :   app_time.toString(),
        "location":   app_location
      }

      console.log(send);

      $scope.showAlert = function() {
        var alertPopup = $ionicPopup.alert({
          title: 'All done!',
          template: 'Appointment Requested'
        });
        alertPopup.then(function(res) {
          $state.go('schedule');
          console.log('Thank you for not eating my delicious ice cream cone');
          setTimeout(function()
          {
              $scope.doRefresh();

          }, 2000);
        });
      };

    var test_date = $filter('date')(app_date, "dd/MM/yyyy");
    var test_time = $filter('date')(app_time, "HH:mm a");

      // Simple GET request example :
    $http.get('http://appointly.mybluemix.net/twiliouth?number=+1' + app_numb + '&message=An appointment has been requested on ' + test_date + ' at ' + test_time + '.').
      success(function(data, status, headers, config) {
        // this callback will be called asynchronously
        // when the response is available
        console.log('MSG sent to phone!');
      }).
      error(function(data, status, headers, config) {
        // called asynchronously if an error occurs
        // or server returns response with an error status.
      });

      // $.ajax({
      //   url: 'http://appointly.mybluemix.net/',
      //   type: 'GET',
      //   contentType: "text/plain; charset=utf-8",
      //   dataType: 'text',
      //   data: send.name + " scheduled an appoinment with you at " + send.time + " on " + send.date + ", location: " + send.location,
      //   // beforeSend: function(xhr) {
      //   //   xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
      //   // },
      //   success: function(data) {
      //     alert("Submitted!");
      //     if (typeof success != 'undefined') {
      //       // jQuery.parseJSON(doc.responseJSON.documents.toSource());
      //       success(data);
      //     }
      //   },
      //   fail: function(data) {
      //     alert('No!');
      //     alert(data.error);
      //     console.log('Fail!');
      //     if (typeof fail != 'undefined') {
      //       fail(data);
      //     }
      //   }
      // })

      $.ajax({
        //removed the /2 from url.
        url: 'https://api-us.clusterpoint.com//100600/Appointly/_insert',
        type: 'POST',
        dataType: 'json',
        data: JSON.stringify(send),
        beforeSend: function(xhr) {
          xhr.setRequestHeader('Authorization', 'Basic ' + btoa('bpshonyak@live.com:Password01'));
        },
        success: function(data) {
          if (typeof success != 'undefined') {
            success(data);
          }
          $scope.showAlert();
        },
        fail: function(data) {
          alert('No!');
          alert(data.error);
          console.log('Fail!');
          if (typeof fail != 'undefined') {
            fail(data);
          }
        }
      })

    }
  })
  .controller('MainCtrl', function($http, $scope) {
    $scope.test = 'Scope Works!';

    // $scope.stories = [];
    //
    // $scope.appointments = info;

    // [{
    //   name: 'Green Valley Clinic',
    //   img_p: 'health.png',
    //   date: 'June 30th',
    //   time: 10,
    //   zone: 'am'
    // }, {
    //   name: 'Silver Point Dental',
    //   img_p: 'dental.png',
    //   date: 'July 16th',
    //   time: 2,
    //   zone: 'pm'
    // }, {
    //   name: 'Multicare',
    //   img_p: 'multicare.png',
    //   date: 'July 25th',
    //   time: 4,
    //   zone: 'pm'
    // }];

  });
<|endoftext|>


<commit_before>import axios from 'axios'
import { EventEmitter } from 'events'

const SCANNER_URL = 'http://localhost:8000/'
axios.defaults.baseURL = SCANNER_URL

export class MediaScanner extends EventEmitter {
  lastSeq = 0
  media = []
  connected = false

  constructor () {
    super()
    this._updateMedia()
  }

  getMediaDuration (name) {
    for (const clip of this.media) {
      if (clip.name === name.toUpperCase()) {
        return clip.format.duration
      }
    }

    return 0
  }

  getFolderContents (name) {
    const res = []

    if (name.substr(-1) !== '/') name += '/'
    name = name.toUpperCase()

    for (const clip of this.media) {
      if (clip.name.search(name) === 0) {
        let clipName = clip.name
        clipName = clipName.replace(name, '')
        if (clipName.split('/').length === 1) {
          res.push(clip)
        }
      }
    }

    return res
  }

  async _updateMedia () {
    try {
      const res = await axios.get('/stat/seq')
      const lastSeq = res.data.update_seq

      if (lastSeq > this.lastSeq) {
        this.lastSeq = lastSeq
        this.media = (await axios.get('/media')).data
        this.emit('changed')
      }

      if (!this.connected) {
        this.connected = true
        this.emit('connected')
      }
    } catch (e) {
      if (this.connected) {
        this.connected = false
        this.emit('disconnected')
      }
    }

    setTimeout(() => this._updateMedia(), 1000)
  }
}
<commit_msg>fix: folder getter should return file names<commit_after>import axios from 'axios'
import { EventEmitter } from 'events'

const SCANNER_URL = 'http://localhost:8000/'
axios.defaults.baseURL = SCANNER_URL

export class MediaScanner extends EventEmitter {
  lastSeq = 0
  media = []
  connected = false

  constructor () {
    super()
    this._updateMedia()
  }

  getMediaDuration (name) {
    for (const clip of this.media) {
      if (clip.name === name.toUpperCase()) {
        return clip.format.duration
      }
    }

    return 0
  }

  getFolderContents (name) {
    const res = []

    if (name.substr(-1) !== '/') name += '/'
    name = name.toUpperCase()

    for (const clip of this.media) {
      if (clip.name.search(name) === 0) {
        let clipName = clip.name
        clipName = clipName.replace(name, '')
        if (clipName.split('/').length === 1) {
          res.push(clip.name)
        }
      }
    }

    return res
  }

  async _updateMedia () {
    try {
      const res = await axios.get('/stat/seq')
      const lastSeq = res.data.update_seq

      if (lastSeq > this.lastSeq) {
        this.lastSeq = lastSeq
        this.media = (await axios.get('/media')).data
        this.emit('changed')
      }

      if (!this.connected) {
        this.connected = true
        this.emit('connected')
      }
    } catch (e) {
      if (this.connected) {
        this.connected = false
        this.emit('disconnected')
      }
    }

    setTimeout(() => this._updateMedia(), 1000)
  }
}
<|endoftext|>


<commit_before>import { checkAction } from './helpers';
import { compose } from 'redux';
import { getRequestMiddlewares } from './requestMiddlewares';

/**
* This is a standard Redux middleware that listens for async actions
* This middleware waits as its first param, a function with the following signature
* done => (action, response) => done(action, error, response)
*
* The actions for this middleware, should look like the following
*  {
*    type: ['REQUEST_TYPE', 'RESPONSE_TYPE'],
*    payload: {},
*    meta: {
*      ...allAdditionalData
*    },
*  }
* @param {Object} asyncTask - function that executes the async task
*/

function execAsyncTask(requestType, asyncTask) {
  return store => next => (action) => {
    store.dispatch({ type: requestType, meta: action.meta });

    const done = (err, response) => err
      ? next(action, err, null)
      : next(action, null, response);

    const options = { payload: action.payload, ...action.meta };
    return asyncTask(store)(done)(options);
  };
}

function handleResponse(responseType) {
  return store => (action, err, response) => {
    const responseAction = {
      type: responseType,
      meta: action.meta,
      payload: response,
    };
    if (err) {
      const actionToDispatch = { ...responseAction, error: true };
      store.dispatch(actionToDispatch);
      return err;
    }
    store.dispatch(responseAction);
    return response;
  };
}

export function createAsyncMiddleware(asyncTask) {
  if (typeof asyncTask !== 'function') {
    const warning =
      'You must provide a asyncTask function to createAsyncMiddleware, with the following signature: ';
    const example = 'done => (action, error, response) => done(action, error, response)';
    throw new Error(`${warning} \n ${example}`)
  }

  return store => next => (action) => {
    const { type, meta } = action;

    if (!Array.isArray(type)) {
      return next(action);
    }

    if (!checkAction(type)) {
      throw new Error('Expected type to be an array of two strings, request and response.');
    }

    if (!meta || typeof meta !== 'object') {
      throw new Error('Expected meta to be an object');
    }

    const requestMiddlewares = getRequestMiddlewares(action.meta.middlewares);
    const [requestType, responseType] = action.type;

    const chain = [
      requestMiddlewares('onRequest'),
      execAsyncTask(requestType, asyncTask),
      requestMiddlewares('onResponse'),
    ].map(middleware => middleware(store));

    const done = handleResponse(responseType)(store);
    return compose(...chain)(done)(action);
  };
}

export default {
  createAsyncMiddleware,
}
<commit_msg>Simplify done creation / use error for payload<commit_after>import { checkAction } from './helpers';
import { compose } from 'redux';
import { getRequestMiddlewares } from './requestMiddlewares';

/**
* This is a standard Redux middleware that listens for async actions
* This middleware waits as its first param, a function with the following signature
* done => (action, response) => done(action, error, response)
*
* The actions for this middleware, should look like the following
*  {
*    type: ['REQUEST_TYPE', 'RESPONSE_TYPE'],
*    payload: {},
*    meta: {
*      ...allAdditionalData
*    },
*  }
* @param {Object} asyncTask - function that executes the async task
*/

function execAsyncTask(requestType, asyncTask) {
  return store => next => (action) => {
    store.dispatch({ type: requestType, meta: action.meta });

    const done = (err, response) => next(action, err, response);

    const options = { payload: action.payload, ...action.meta };
    return asyncTask(store)(done)(options);
  };
}

function handleResponse(responseType) {
  return store => (action, err, response) => {
    const responseAction = {
      type: responseType,
      meta: action.meta,
      payload: response,
    };
    if (err) {
      const actionToDispatch = { ...responseAction, error: true, payload: err };
      store.dispatch(actionToDispatch);
      return err;
    }
    store.dispatch(responseAction);
    return response;
  };
}

export function createAsyncMiddleware(asyncTask) {
  if (typeof asyncTask !== 'function') {
    const warning =
      'You must provide a asyncTask function to createAsyncMiddleware, with the following signature: ';
    const example = 'done => (action, error, response) => done(action, error, response)';
    throw new Error(`${warning} \n ${example}`)
  }

  return store => next => (action) => {
    const { type, meta } = action;

    if (!Array.isArray(type)) {
      return next(action);
    }

    if (!checkAction(type)) {
      throw new Error('Expected type to be an array of two strings, request and response.');
    }

    if (!meta || typeof meta !== 'object') {
      throw new Error('Expected meta to be an object');
    }

    const requestMiddlewares = getRequestMiddlewares(action.meta.middlewares);
    const [requestType, responseType] = action.type;

    const chain = [
      requestMiddlewares('onRequest'),
      execAsyncTask(requestType, asyncTask),
      requestMiddlewares('onResponse'),
    ].map(middleware => middleware(store));

    const done = handleResponse(responseType)(store);
    return compose(...chain)(done)(action);
  };
}

export default {
  createAsyncMiddleware,
}
<|endoftext|>


<commit_before>import {Fragment} from "./fragment"
import {Mark} from "./mark"
import {Slice, replace} from "./replace"
import {ResolvedPos} from "./resolvedpos"

const emptyArray = [], emptyAttrs = Object.create(null)

// ;; This class represents a node in the tree that makes up a
// ProseMirror document. So a document is an instance of `Node`, with
// children that are also instances of `Node`.
//
// Nodes are persistent data structures. Instead of changing them, you
// create new ones with the content you want. Old ones keep pointing
// at the old document shape. This is made cheaper by sharing
// structure between the old and new data as much as possible, which a
// tree shape like this (without back pointers) makes easy.
//
// **Never** directly mutate the properties of a `Node` object. See
// [this guide](guide/doc.html) for more information.
export class Node {
  constructor(type, attrs, content, marks) {
    // :: NodeType
    // The type of node that this is.
    this.type = type

    // :: Object
    // An object mapping attribute names to string values. The kind of
    // attributes allowed and required are determined by the node
    // type.
    this.attrs = attrs

    // :: Fragment
    // The node's content.
    this.content = content || Fragment.empty

    // :: [Mark]
    // The marks (things like whether it is emphasized or part of a
    // link) associated with this node.
    this.marks = marks || emptyArray
  }

  // :: number
  // The size of this node. For text node, this is the amount of
  // characters. For leaf nodes, it is one. And for non-leaf nodes, it
  // is the size of the content plus two (the start and end token).
  get nodeSize() { return this.type.contains ? 2 + this.content.size : 1 }

  // :: number
  // The number of children that the node has.
  get childCount() { return this.content.childCount }

  // :: (number) → Node
  // Get the child node at the given index. Raise an error when the
  // index is out of range.
  child(index) { return this.content.child(index) }

  // :: (number) → ?Node
  // Get the child node at the given index, if it exists.
  maybeChild(index) { return this.content.maybeChild(index) }

  // :: ((node: Node, offset: number))
  // Call `f` for every child node, passing the node and its offset
  // into this parent node.
  forEach(f) { this.content.forEach(f) }

  // :: string
  // Concatenate all the text nodes found in this fragment and its
  // children.
  get textContent() { return this.content.textContent }

  // :: ?Node
  // Returns this node's first child, or `null` if there are no
  // children.
  get firstChild() { return this.content.firstChild }

  // :: ?Node
  // Returns this node's last child, or `null` if there are no
  // children.
  get lastChild() { return this.content.lastChild }

  // :: (Node) → bool
  // Test whether two nodes represent the same content.
  eq(other) {
    return this == other || (this.sameMarkup(other) && this.content.eq(other.content))
  }

  // :: (Node) → bool
  // Compare the markup (type, attributes, and marks) of this node to
  // those of another. Returns `true` if both have the same markup.
  sameMarkup(other) {
    return this.hasMarkup(other.type, other.attrs, other.marks)
  }

  // :: (NodeType, ?Object, ?[Mark]) → bool
  // Check whether this node's markup correspond to the given type,
  // attributes, and marks.
  hasMarkup(type, attrs, marks) {
    return this.type == type && Node.sameAttrs(this.attrs, attrs || emptyAttrs) && Mark.sameSet(this.marks, marks || emptyArray)
  }

  static sameAttrs(a, b) {
    if (a == b) return true
    for (let prop in a)
      if (a[prop] !== b[prop]) return false
    return true
  }

  // :: (?Fragment) → Node
  // Create a new node with the same markup as this node, containing
  // the given content (or empty, if no content is given).
  copy(content = null) {
    if (content == this.content) return this
    return new this.constructor(this.type, this.attrs, content, this.marks)
  }

  // :: ([Mark]) → Node
  // Create a copy of this node, with the given set of marks instead
  // of the node's own marks.
  mark(marks) {
    return new this.constructor(this.type, this.attrs, this.content, marks)
  }

  // :: (number, ?number) → Node
  // Create a copy of this node with only the content between the
  // given offsets. If `to` is not given, it defaults to the end of
  // the node.
  cut(from, to) {
    if (from == 0 && to == this.content.size) return this
    return this.copy(this.content.cut(from, to))
  }

  // :: (number, ?number) → Slice
  // Cut out the part of the document between the given positions, and
  // return it as a `Slice` object.
  slice(from, to = this.content.size) {
    if (from == to) return Slice.empty

    let $from = this.resolve(from), $to = this.resolve(to)
    let depth = $from.sameDepth($to), start = $from.start(depth)
    let content = $from.node(depth).content.cut($from.pos - start, $to.pos - start)
    return new Slice(content, $from.depth - depth, $to.depth - depth)
  }

  // :: (number, number, Slice) → Node
  // Replace the part of the document between the given positions with
  // the given slice. The slice must 'fit', meaning its open sides
  // must be able to connect to the surrounding content, and its
  // content nodes must be valid children for the node they are placed
  // into. If any of this is violated, an error of type `ReplaceError`
  // is thrown.
  replace(from, to, slice) {
    return replace(this.resolve(from), this.resolve(to), slice)
  }

  // :: (number) → ?Node
  // Find the node after the given position.
  nodeAt(pos) {
    for (let node = this;;) {
      let {index, offset} = node.content.findIndex(pos)
      node = node.maybeChild(index)
      if (!node) return null
      if (offset == pos || node.isText) return node
      pos -= offset + 1
    }
  }

  // :: (number) → {node: ?Node, index: number, offset: number}
  // Find the (direct) child node after the given offset, if any,
  // and return it along with its index and offset relative to this
  // node.
  nodeAfter(pos) {
    let {index, offset} = this.content.findIndex(pos)
    return {node: this.content.maybeChild(index), index, offset}
  }

  // :: (number) → {node: ?Node, index: number, offset: number}
  // Find the (direct) child node before the given offset, if any,
  // and return it along with its index and offset relative to this
  // node.
  nodeBefore(pos) {
    if (pos == 0) return {node: null, index: 0, offset: 0}
    let {index, offset} = this.content.findIndex(pos)
    if (offset < pos) return {node: this.content.child(index), index, offset}
    let node = this.content.child(index - 1)
    return {node, index: index - 1, offset: offset - node.nodeSize}
  }

  // :: (?number, ?number, (node: Node, pos: number, parent: Node))
  // Iterate over all nodes between the given two positions, calling
  // the callback with the node, its position, and its parent
  // node. `from` and `to` may be left off, to denote
  // starting at the start of the node or ending at its end.
  nodesBetween(from, to, f, pos = 0) {
    this.content.nodesBetween(from, to, f, pos, this)
  }

  // :: (number) → ResolvedPos
  // Resolve the given position in the document, returning an object
  // describing its path through the document.
  resolve(pos) { return ResolvedPos.resolveCached(this, pos) }

  resolveNoCache(pos) { return ResolvedPos.resolve(this, pos) }

  // :: (number) → [Mark]
  // Get the marks at the given position factoring in the surrounding marks'
  // inclusiveLeft and inclusiveRight properties. If the position is at the
  // start of a non-empty node, the marks of the node after it are returned.
  marksAt(pos) {
    let $pos = this.resolve(pos), parent = $pos.parent, index = $pos.index($pos.depth)

    // In an empty parent, return the empty array
    if (parent.content.size == 0) return emptyArray
    // When inside a text node or at the start of the parent node, return the node's marks
    if (index == 0 || !$pos.atNodeBoundary) return parent.child(index).marks

    let marks = parent.child(index - 1).marks
    for (var i = 0; i < marks.length; i++) if (!marks[i].type.inclusiveRight)
      marks = marks[i--].removeFromSet(marks)
    return marks
  }

  // :: (?number, ?number, MarkType) → bool
  // Test whether a mark of the given type occurs in this document
  // between the two given positions.
  rangeHasMark(from, to, type) {
    let found = false
    this.nodesBetween(from, to, node => {
      if (type.isInSet(node.marks)) found = true
      return !found
    })
    return found
  }

  // :: bool
  // True when this is a block (non-inline node)
  get isBlock() { return this.type.isBlock }

  // :: bool
  // True when this is a textblock node, a block node with inline
  // content.
  get isTextblock() { return this.type.isTextblock }

  // :: bool
  // True when this is an inline node (a text node or a node that can
  // appear among text).
  get isInline() { return this.type.isInline }

  // :: bool
  // True when this is a text node.
  get isText() { return this.type.isText }

  // :: () → string
  // Return a string representation of this node for debugging
  // purposes.
  toString() {
    let name = this.type.name
    if (this.content.size)
      name += "(" + this.content.toStringInner() + ")"
    return wrapMarks(this.marks, name)
  }

  // :: () → Object
  // Return a JSON-serializeable representation of this node.
  toJSON() {
    let obj = {type: this.type.name}
    for (let _ in this.attrs) {
      obj.attrs = this.attrs
      break
    }
    if (this.content.size)
      obj.content = this.content.toJSON()
    if (this.marks.length)
      obj.marks = this.marks.map(n => n.toJSON())
    return obj
  }

  // :: (Schema, Object) → Node
  // Deserialize a node from its JSON representation.
  static fromJSON(schema, json) {
    let type = schema.nodeType(json.type)
    let content = json.text != null ? json.text : Fragment.fromJSON(schema, json.content)
    return type.create(json.attrs, content, json.marks && json.marks.map(schema.markFromJSON))
  }

  // This is a hack to be able to treat a node object as an iterator result
  get value() { return this }
}

// ;; #forward=Node
export class TextNode extends Node {
  constructor(type, attrs, content, marks) {
    super(type, attrs, null, marks)

    if (!content) throw new RangeError("Empty text nodes are not allowed")

    // :: ?string
    // For text nodes, this contains the node's text content.
    this.text = content
  }

  toString() { return wrapMarks(this.marks, JSON.stringify(this.text)) }

  get textContent() { return this.text }

  get nodeSize() { return this.text.length }

  mark(marks) {
    return new TextNode(this.type, this.attrs, this.text, marks)
  }

  cut(from = 0, to = this.text.length) {
    if (from == 0 && to == this.text.length) return this
    return this.copy(this.text.slice(from, to))
  }

  eq(other) {
    return this.sameMarkup(other) && this.text == other.text
  }

  toJSON() {
    let base = super.toJSON()
    base.text = this.text
    return base
  }
}

function wrapMarks(marks, str) {
  for (let i = marks.length - 1; i >= 0; i--)
    str = marks[i].type.name + "(" + str + ")"
  return str
}
<commit_msg>Add Node.descendants<commit_after>import {Fragment} from "./fragment"
import {Mark} from "./mark"
import {Slice, replace} from "./replace"
import {ResolvedPos} from "./resolvedpos"

const emptyArray = [], emptyAttrs = Object.create(null)

// ;; This class represents a node in the tree that makes up a
// ProseMirror document. So a document is an instance of `Node`, with
// children that are also instances of `Node`.
//
// Nodes are persistent data structures. Instead of changing them, you
// create new ones with the content you want. Old ones keep pointing
// at the old document shape. This is made cheaper by sharing
// structure between the old and new data as much as possible, which a
// tree shape like this (without back pointers) makes easy.
//
// **Never** directly mutate the properties of a `Node` object. See
// [this guide](guide/doc.html) for more information.
export class Node {
  constructor(type, attrs, content, marks) {
    // :: NodeType
    // The type of node that this is.
    this.type = type

    // :: Object
    // An object mapping attribute names to string values. The kind of
    // attributes allowed and required are determined by the node
    // type.
    this.attrs = attrs

    // :: Fragment
    // The node's content.
    this.content = content || Fragment.empty

    // :: [Mark]
    // The marks (things like whether it is emphasized or part of a
    // link) associated with this node.
    this.marks = marks || emptyArray
  }

  // :: number
  // The size of this node. For text node, this is the amount of
  // characters. For leaf nodes, it is one. And for non-leaf nodes, it
  // is the size of the content plus two (the start and end token).
  get nodeSize() { return this.type.contains ? 2 + this.content.size : 1 }

  // :: number
  // The number of children that the node has.
  get childCount() { return this.content.childCount }

  // :: (number) → Node
  // Get the child node at the given index. Raise an error when the
  // index is out of range.
  child(index) { return this.content.child(index) }

  // :: (number) → ?Node
  // Get the child node at the given index, if it exists.
  maybeChild(index) { return this.content.maybeChild(index) }

  // :: ((node: Node, offset: number))
  // Call `f` for every child node, passing the node and its offset
  // into this parent node.
  forEach(f) { this.content.forEach(f) }

  // :: string
  // Concatenate all the text nodes found in this fragment and its
  // children.
  get textContent() { return this.content.textContent }

  // :: ?Node
  // Returns this node's first child, or `null` if there are no
  // children.
  get firstChild() { return this.content.firstChild }

  // :: ?Node
  // Returns this node's last child, or `null` if there are no
  // children.
  get lastChild() { return this.content.lastChild }

  // :: (Node) → bool
  // Test whether two nodes represent the same content.
  eq(other) {
    return this == other || (this.sameMarkup(other) && this.content.eq(other.content))
  }

  // :: (Node) → bool
  // Compare the markup (type, attributes, and marks) of this node to
  // those of another. Returns `true` if both have the same markup.
  sameMarkup(other) {
    return this.hasMarkup(other.type, other.attrs, other.marks)
  }

  // :: (NodeType, ?Object, ?[Mark]) → bool
  // Check whether this node's markup correspond to the given type,
  // attributes, and marks.
  hasMarkup(type, attrs, marks) {
    return this.type == type && Node.sameAttrs(this.attrs, attrs || emptyAttrs) && Mark.sameSet(this.marks, marks || emptyArray)
  }

  static sameAttrs(a, b) {
    if (a == b) return true
    for (let prop in a)
      if (a[prop] !== b[prop]) return false
    return true
  }

  // :: (?Fragment) → Node
  // Create a new node with the same markup as this node, containing
  // the given content (or empty, if no content is given).
  copy(content = null) {
    if (content == this.content) return this
    return new this.constructor(this.type, this.attrs, content, this.marks)
  }

  // :: ([Mark]) → Node
  // Create a copy of this node, with the given set of marks instead
  // of the node's own marks.
  mark(marks) {
    return new this.constructor(this.type, this.attrs, this.content, marks)
  }

  // :: (number, ?number) → Node
  // Create a copy of this node with only the content between the
  // given offsets. If `to` is not given, it defaults to the end of
  // the node.
  cut(from, to) {
    if (from == 0 && to == this.content.size) return this
    return this.copy(this.content.cut(from, to))
  }

  // :: (number, ?number) → Slice
  // Cut out the part of the document between the given positions, and
  // return it as a `Slice` object.
  slice(from, to = this.content.size) {
    if (from == to) return Slice.empty

    let $from = this.resolve(from), $to = this.resolve(to)
    let depth = $from.sameDepth($to), start = $from.start(depth)
    let content = $from.node(depth).content.cut($from.pos - start, $to.pos - start)
    return new Slice(content, $from.depth - depth, $to.depth - depth)
  }

  // :: (number, number, Slice) → Node
  // Replace the part of the document between the given positions with
  // the given slice. The slice must 'fit', meaning its open sides
  // must be able to connect to the surrounding content, and its
  // content nodes must be valid children for the node they are placed
  // into. If any of this is violated, an error of type `ReplaceError`
  // is thrown.
  replace(from, to, slice) {
    return replace(this.resolve(from), this.resolve(to), slice)
  }

  // :: (number) → ?Node
  // Find the node after the given position.
  nodeAt(pos) {
    for (let node = this;;) {
      let {index, offset} = node.content.findIndex(pos)
      node = node.maybeChild(index)
      if (!node) return null
      if (offset == pos || node.isText) return node
      pos -= offset + 1
    }
  }

  // :: (number) → {node: ?Node, index: number, offset: number}
  // Find the (direct) child node after the given offset, if any,
  // and return it along with its index and offset relative to this
  // node.
  nodeAfter(pos) {
    let {index, offset} = this.content.findIndex(pos)
    return {node: this.content.maybeChild(index), index, offset}
  }

  // :: (number) → {node: ?Node, index: number, offset: number}
  // Find the (direct) child node before the given offset, if any,
  // and return it along with its index and offset relative to this
  // node.
  nodeBefore(pos) {
    if (pos == 0) return {node: null, index: 0, offset: 0}
    let {index, offset} = this.content.findIndex(pos)
    if (offset < pos) return {node: this.content.child(index), index, offset}
    let node = this.content.child(index - 1)
    return {node, index: index - 1, offset: offset - node.nodeSize}
  }

  // :: (?number, ?number, (node: Node, pos: number, parent: Node))
  // Iterate over all nodes between the given two positions, calling
  // the callback with the node, its position, and its parent
  // node. `from` and `to` may be left off, to denote
  // starting at the start of the node or ending at its end.
  nodesBetween(from, to, f, pos = 0) {
    this.content.nodesBetween(from, to, f, pos, this)
  }

  // :: ((node: Node, pos: number, parent: Node))
  // Call the given callback for every descendant node.
  descendants(f) {
    this.nodesBetween(0, this.content.size, f)
  }

  // :: (number) → ResolvedPos
  // Resolve the given position in the document, returning an object
  // describing its path through the document.
  resolve(pos) { return ResolvedPos.resolveCached(this, pos) }

  resolveNoCache(pos) { return ResolvedPos.resolve(this, pos) }

  // :: (number) → [Mark]
  // Get the marks at the given position factoring in the surrounding marks'
  // inclusiveLeft and inclusiveRight properties. If the position is at the
  // start of a non-empty node, the marks of the node after it are returned.
  marksAt(pos) {
    let $pos = this.resolve(pos), parent = $pos.parent, index = $pos.index($pos.depth)

    // In an empty parent, return the empty array
    if (parent.content.size == 0) return emptyArray
    // When inside a text node or at the start of the parent node, return the node's marks
    if (index == 0 || !$pos.atNodeBoundary) return parent.child(index).marks

    let marks = parent.child(index - 1).marks
    for (var i = 0; i < marks.length; i++) if (!marks[i].type.inclusiveRight)
      marks = marks[i--].removeFromSet(marks)
    return marks
  }

  // :: (?number, ?number, MarkType) → bool
  // Test whether a mark of the given type occurs in this document
  // between the two given positions.
  rangeHasMark(from, to, type) {
    let found = false
    this.nodesBetween(from, to, node => {
      if (type.isInSet(node.marks)) found = true
      return !found
    })
    return found
  }

  // :: bool
  // True when this is a block (non-inline node)
  get isBlock() { return this.type.isBlock }

  // :: bool
  // True when this is a textblock node, a block node with inline
  // content.
  get isTextblock() { return this.type.isTextblock }

  // :: bool
  // True when this is an inline node (a text node or a node that can
  // appear among text).
  get isInline() { return this.type.isInline }

  // :: bool
  // True when this is a text node.
  get isText() { return this.type.isText }

  // :: () → string
  // Return a string representation of this node for debugging
  // purposes.
  toString() {
    let name = this.type.name
    if (this.content.size)
      name += "(" + this.content.toStringInner() + ")"
    return wrapMarks(this.marks, name)
  }

  // :: () → Object
  // Return a JSON-serializeable representation of this node.
  toJSON() {
    let obj = {type: this.type.name}
    for (let _ in this.attrs) {
      obj.attrs = this.attrs
      break
    }
    if (this.content.size)
      obj.content = this.content.toJSON()
    if (this.marks.length)
      obj.marks = this.marks.map(n => n.toJSON())
    return obj
  }

  // :: (Schema, Object) → Node
  // Deserialize a node from its JSON representation.
  static fromJSON(schema, json) {
    let type = schema.nodeType(json.type)
    let content = json.text != null ? json.text : Fragment.fromJSON(schema, json.content)
    return type.create(json.attrs, content, json.marks && json.marks.map(schema.markFromJSON))
  }

  // This is a hack to be able to treat a node object as an iterator result
  get value() { return this }
}

// ;; #forward=Node
export class TextNode extends Node {
  constructor(type, attrs, content, marks) {
    super(type, attrs, null, marks)

    if (!content) throw new RangeError("Empty text nodes are not allowed")

    // :: ?string
    // For text nodes, this contains the node's text content.
    this.text = content
  }

  toString() { return wrapMarks(this.marks, JSON.stringify(this.text)) }

  get textContent() { return this.text }

  get nodeSize() { return this.text.length }

  mark(marks) {
    return new TextNode(this.type, this.attrs, this.text, marks)
  }

  cut(from = 0, to = this.text.length) {
    if (from == 0 && to == this.text.length) return this
    return this.copy(this.text.slice(from, to))
  }

  eq(other) {
    return this.sameMarkup(other) && this.text == other.text
  }

  toJSON() {
    let base = super.toJSON()
    base.text = this.text
    return base
  }
}

function wrapMarks(marks, str) {
  for (let i = marks.length - 1; i >= 0; i--)
    str = marks[i].type.name + "(" + str + ")"
  return str
}
<|endoftext|>


<commit_before>const Hex = require('../hex');

function _coords_partial_cmp(a, b) {
  const ydiff = a.y-b.y;
  if (ydiff != 0) {
    return ydiff;
  }
  return a.x - b.x;
}

class Unit {
  constructor (pivot, members, rotation = 0) {
    // NOTE: Why cloning? Do these ever get modified?
    this.pivot = JSON.parse(JSON.stringify(pivot));
    this.members = JSON.parse(JSON.stringify(members));
    this.rotation = rotation;
    this.hashValue = null;
  }

  hash () {
    if (!this.hashValue) {
      var h = `${this.pivot.x}|${this.pivot.y}`;
      this.members.forEach((m) => {
        h += `|${m.x};${m.y}`;
      });
      this.hashValue = h;
    }
    return this.hashValue;
  }

  equals(other) {
    if ((this.pivot.x != other.pivot.x) || (this.pivot.y != other.pivot.y)) {
      return false
    }
    if (this.members.length != other.members.length) {
      return false
    }
    return this.equalMembers(other)
  }

  equalMembers(other) {
    if (this.members.length != other.members.length) {
      return false
    }
    const m = this.members.slice(0).sort(_coords_partial_cmp);
    const o = other.members.slice(0).sort(_coords_partial_cmp);
    for (var i=0; i<m.length; i++) {
      if ((m[i].x != o[i].x) || (m[i].y != o[i].y)) {
        return false
      }
    }
    return true;
  }

  move (direction) {

    var deltaXOdd = 0, deltaXEven = 0, deltaY = 0;
    switch(direction) {
        case 'SW':
          deltaXOdd = 0;
          deltaXEven = -1;
          deltaY = 1;
          break;
        case 'SE':
          deltaXOdd = 1;
          deltaXEven = 0;
          deltaY = 1;
          break;
        case 'W':
          deltaXOdd = -1;
          deltaXEven = -1;
          break;
        case 'E':
          deltaXOdd = 1;
          deltaXEven = 1;
          break;
        case 'CW':
        case 'CCW':
          return this.rotate(direction);
          break;

    }
    var pivot = { x: this.pivot.x + (this.pivot.y%2 ? deltaXOdd : deltaXEven), y: this.pivot.y + deltaY};
    var members = [];

    this.members.forEach(function(item, i, arr) {
      members.push({ x: item.x + ((item.y%2) ? deltaXOdd : deltaXEven), y: item.y + deltaY} );
    });

    return new Unit(pivot, members);
  }

  moveNc (direction) {
    var deltaXOdd = 0, deltaXEven = 0, deltaY = 0;
    switch(direction) {
        case 'SW':
          deltaXOdd = 0;
          deltaXEven = -1;
          deltaY = 1;
          break;
        case 'SE':
          deltaXOdd = 1;
          deltaXEven = 0;
          deltaY = 1;
          break;
        case 'W':
          deltaXOdd = -1;
          deltaXEven = -1;
          break;
        case 'E':
          deltaXOdd = 1;
          deltaXEven = 1;
          break;
        case 'CW':
        case 'CCW':
          return this.rotate(direction);
          break;
    }
    this.pivot.x += this.pivot.y%2 ? deltaXOdd : deltaXEven;
    this.pivot.y += deltaY;

    this.members.forEach(function(item, i, arr) {
      item.x += (item.y%2) ? deltaXOdd : deltaXEven;
      item.y += deltaY;
    });
  }

  rotate (direction) {
    var rotation = this.rotation + (direction == 'CW' ? 1 : -1);

    if (rotation >=6)
      rotation -= 6;

    if (rotation < 0)
      rotation += 6;

    var hex_pivot = Hex.offset_to_cube(this.pivot);
    var hex_members = this.members.map(p => Hex.offset_to_cube(p));

    // move hex coordinates so pivot is a center of coordinates
    var centered_members = hex_members.map(p => Hex.hex_subtract(p, hex_pivot));

    var rotated = centered_members.map(p => Unit.rotate_hex_cell(p, direction));

    var move_back = rotated.map(p => Hex.hex_add(p, hex_pivot));

    var new_members = move_back.map(p => Hex.offset_from_cube(p));

    return new Unit(this.pivot, new_members, rotation);
  }

  getMembers () {
    return this.members;
  }

  getSize () {
    var min_x = this.members[0].x;
    var min_y = this.members[0].y;
    var max_x = this.members[0].x;
    var max_y = this.members[0].y;
    for (var i = 1; i < this.members.length; i++) {
      if (this.members[i].x > max_x)
        max_x = this.members[i].x;
      if (this.members[i].y > max_y)
        max_y = this.members[i].y;
      if (this.members[i].x < min_x)
        min_x = this.members[i].x;
      if (this.members[i].y < min_y)
        min_y = this.members[i].y;
    }

    return {x: (max_x - min_x + 1), y: (max_y - min_y + 1), min: {x: min_x, y: min_y}, max: {x: max_x, y: max_y}};
  }

  moveBy (from, to) {
    var hexOffset = Hex.hex_subtract(Hex.offset_to_cube(to), Hex.offset_to_cube(from));
    var newPivot = Hex.offset_from_cube(Hex.hex_add(hexOffset, Hex.offset_to_cube(this.pivot)));
    var newMembers = this.members.map(m => Hex.offset_from_cube(Hex.hex_add(hexOffset, Hex.offset_to_cube(m))));
    return new Unit(newPivot, newMembers, this.rotation);
  }

  moveTo (newPivot) {
    var hexOffset = Hex.hex_subtract(Hex.offset_to_cube(newPivot), Hex.offset_to_cube(this.pivot));
    var newMembers = this.members.map(m => Hex.offset_from_cube(Hex.hex_add(hexOffset, Hex.offset_to_cube(m))));
    return new Unit(newPivot, newMembers, this.rotation);
  }

  minPivotDistance () {
    var cube_pivot = Hex.offset_to_cube(this.pivot);
    var distance = 0;
    for(var i=0; i < this.members.length; i++) {
      var cube_point = Hex.offset_to_cube(this.members[i]);
      var d = Hex.hex_distance(cube_pivot, cube_point);
      if(i == 0)
        distance = d;
      else if(d < distance)
        distance = d;
    }

    return distance;

  }

  oneDirectionFromPivot () {
    var weights = {N: 0, S:0, E:0, W:0};
    for(var i=0; i < this.members.length; i++) {
      if(this.members[i].x > this.pivot.x)
        weights.E++;
      else if(this.members[i].x < this.pivot.x)
        weights.W++;
      else {
        weights.E++;
        weights.W++;
      }

      if(this.members[i].y > this.pivot.y)
        weights.S++;
      else if(this.members[i].y < this.pivot.y)
        weights.N++;
      else {
        weights.S++;
        weights.N++;
      }

    }
    if((weights.N && !weights.S) || (!weights.N && weights.S))
      return true;
    if((weights.E && !weights.W) || (!weights.E && weights.W))
      return true;
    return false;
  }

  toString() {
    return JSON.stringify(this);
  }

}

Unit.rotate_cell = (c, direction) => {
  var hex = Hex.offset_to_cube(c);
  return Hex.offset_from_cube(Unit.rotate_hex_cell(hex, direction));
}

Unit.rotate_hex_cell = (hex, direction) => {

  var x = hex.q;
  var y = hex.s;
  var z = hex.r;

  if (direction == 'CW') {
    [x, y, z] = [-z, -x, -y];
  } else if (direction == 'CCW') {
    [x, y, z] = [-y, -z, -x];
  } else {
    throw new Error('Bad rotation direction');
  }

  return new Hex.Hex(x, z, y);

}

module.exports = Unit;
<commit_msg>cosmetics: duplicate code<commit_after>const Hex = require('../hex');

function _coords_partial_cmp(a, b) {
  const ydiff = a.y-b.y;
  if (ydiff != 0) {
    return ydiff;
  }
  return a.x - b.x;
}

class Unit {
  constructor (pivot, members, rotation = 0) {
    // NOTE: Why cloning? Do these ever get modified?
    this.pivot = JSON.parse(JSON.stringify(pivot));
    this.members = JSON.parse(JSON.stringify(members));
    this.rotation = rotation;
    this.hashValue = null;
  }

  hash () {
    if (!this.hashValue) {
      var h = `${this.pivot.x}|${this.pivot.y}`;
      this.members.forEach((m) => {
        h += `|${m.x};${m.y}`;
      });
      this.hashValue = h;
    }
    return this.hashValue;
  }

  equals(other) {
    if ((this.pivot.x != other.pivot.x) || (this.pivot.y != other.pivot.y)) {
      return false
    }
    return this.equalMembers(other)
  }

  equalMembers(other) {
    if (this.members.length != other.members.length) {
      return false
    }
    const m = this.members.slice(0).sort(_coords_partial_cmp);
    const o = other.members.slice(0).sort(_coords_partial_cmp);
    for (var i=0; i<m.length; i++) {
      if ((m[i].x != o[i].x) || (m[i].y != o[i].y)) {
        return false
      }
    }
    return true;
  }

  move (direction) {

    var deltaXOdd = 0, deltaXEven = 0, deltaY = 0;
    switch(direction) {
        case 'SW':
          deltaXOdd = 0;
          deltaXEven = -1;
          deltaY = 1;
          break;
        case 'SE':
          deltaXOdd = 1;
          deltaXEven = 0;
          deltaY = 1;
          break;
        case 'W':
          deltaXOdd = -1;
          deltaXEven = -1;
          break;
        case 'E':
          deltaXOdd = 1;
          deltaXEven = 1;
          break;
        case 'CW':
        case 'CCW':
          return this.rotate(direction);
          break;

    }
    var pivot = { x: this.pivot.x + (this.pivot.y%2 ? deltaXOdd : deltaXEven), y: this.pivot.y + deltaY};
    var members = [];

    this.members.forEach(function(item, i, arr) {
      members.push({ x: item.x + ((item.y%2) ? deltaXOdd : deltaXEven), y: item.y + deltaY} );
    });

    return new Unit(pivot, members);
  }

  moveNc (direction) {
    var deltaXOdd = 0, deltaXEven = 0, deltaY = 0;
    switch(direction) {
        case 'SW':
          deltaXOdd = 0;
          deltaXEven = -1;
          deltaY = 1;
          break;
        case 'SE':
          deltaXOdd = 1;
          deltaXEven = 0;
          deltaY = 1;
          break;
        case 'W':
          deltaXOdd = -1;
          deltaXEven = -1;
          break;
        case 'E':
          deltaXOdd = 1;
          deltaXEven = 1;
          break;
        case 'CW':
        case 'CCW':
          return this.rotate(direction);
          break;
    }
    this.pivot.x += this.pivot.y%2 ? deltaXOdd : deltaXEven;
    this.pivot.y += deltaY;

    this.members.forEach(function(item, i, arr) {
      item.x += (item.y%2) ? deltaXOdd : deltaXEven;
      item.y += deltaY;
    });
  }

  rotate (direction) {
    var rotation = this.rotation + (direction == 'CW' ? 1 : -1);

    if (rotation >=6)
      rotation -= 6;

    if (rotation < 0)
      rotation += 6;

    var hex_pivot = Hex.offset_to_cube(this.pivot);
    var hex_members = this.members.map(p => Hex.offset_to_cube(p));

    // move hex coordinates so pivot is a center of coordinates
    var centered_members = hex_members.map(p => Hex.hex_subtract(p, hex_pivot));

    var rotated = centered_members.map(p => Unit.rotate_hex_cell(p, direction));

    var move_back = rotated.map(p => Hex.hex_add(p, hex_pivot));

    var new_members = move_back.map(p => Hex.offset_from_cube(p));

    return new Unit(this.pivot, new_members, rotation);
  }

  getMembers () {
    return this.members;
  }

  getSize () {
    var min_x = this.members[0].x;
    var min_y = this.members[0].y;
    var max_x = this.members[0].x;
    var max_y = this.members[0].y;
    for (var i = 1; i < this.members.length; i++) {
      if (this.members[i].x > max_x)
        max_x = this.members[i].x;
      if (this.members[i].y > max_y)
        max_y = this.members[i].y;
      if (this.members[i].x < min_x)
        min_x = this.members[i].x;
      if (this.members[i].y < min_y)
        min_y = this.members[i].y;
    }

    return {x: (max_x - min_x + 1), y: (max_y - min_y + 1), min: {x: min_x, y: min_y}, max: {x: max_x, y: max_y}};
  }

  moveBy (from, to) {
    var hexOffset = Hex.hex_subtract(Hex.offset_to_cube(to), Hex.offset_to_cube(from));
    var newPivot = Hex.offset_from_cube(Hex.hex_add(hexOffset, Hex.offset_to_cube(this.pivot)));
    var newMembers = this.members.map(m => Hex.offset_from_cube(Hex.hex_add(hexOffset, Hex.offset_to_cube(m))));
    return new Unit(newPivot, newMembers, this.rotation);
  }

  moveTo (newPivot) {
    var hexOffset = Hex.hex_subtract(Hex.offset_to_cube(newPivot), Hex.offset_to_cube(this.pivot));
    var newMembers = this.members.map(m => Hex.offset_from_cube(Hex.hex_add(hexOffset, Hex.offset_to_cube(m))));
    return new Unit(newPivot, newMembers, this.rotation);
  }

  minPivotDistance () {
    var cube_pivot = Hex.offset_to_cube(this.pivot);
    var distance = 0;
    for(var i=0; i < this.members.length; i++) {
      var cube_point = Hex.offset_to_cube(this.members[i]);
      var d = Hex.hex_distance(cube_pivot, cube_point);
      if(i == 0)
        distance = d;
      else if(d < distance)
        distance = d;
    }

    return distance;

  }

  oneDirectionFromPivot () {
    var weights = {N: 0, S:0, E:0, W:0};
    for(var i=0; i < this.members.length; i++) {
      if(this.members[i].x > this.pivot.x)
        weights.E++;
      else if(this.members[i].x < this.pivot.x)
        weights.W++;
      else {
        weights.E++;
        weights.W++;
      }

      if(this.members[i].y > this.pivot.y)
        weights.S++;
      else if(this.members[i].y < this.pivot.y)
        weights.N++;
      else {
        weights.S++;
        weights.N++;
      }

    }
    if((weights.N && !weights.S) || (!weights.N && weights.S))
      return true;
    if((weights.E && !weights.W) || (!weights.E && weights.W))
      return true;
    return false;
  }

  toString() {
    return JSON.stringify(this);
  }

}

Unit.rotate_cell = (c, direction) => {
  var hex = Hex.offset_to_cube(c);
  return Hex.offset_from_cube(Unit.rotate_hex_cell(hex, direction));
}

Unit.rotate_hex_cell = (hex, direction) => {

  var x = hex.q;
  var y = hex.s;
  var z = hex.r;

  if (direction == 'CW') {
    [x, y, z] = [-z, -x, -y];
  } else if (direction == 'CCW') {
    [x, y, z] = [-y, -z, -x];
  } else {
    throw new Error('Bad rotation direction');
  }

  return new Hex.Hex(x, z, y);

}

module.exports = Unit;
<|endoftext|>


<commit_before>// All things navigation - touch navigation and mouse
var Navigation = function (carousel, anim) {
    var $el = carousel.$el,
        options = carousel.options,
        vars = carousel.vars,
        swipeOut = carousel.swipeOut;

    // Start navigation listeners
    $el.children().on('mousedown touchstart', function (e) {
        touchstart.call(this, e);
    });
    $(window).on('mouseup touchend', function (e) {
        touchend(e);
    });

    // And the navigation functions

    // Navigation Variables
    var swipeStartTime, isDown, prevent, startPointX, startPointY, vertical_pan = false,
        horizontal_pan;

    // Swipe out Variables
    var verticalSlideFirstTimeCount;

    // Getter for vertical_pan
    this.get_vertical_pan = function () {
        return vertical_pan
    };

    function touchstart(e) {
        // no-drag feature
        if ($(e.target).attr('no-drag') === 'true' || !$el.end_animation) {
            //Or if hasn't ended swipe out escape
            return;
        }

        var touch;

        //Check for touch event or mousemove
        if (e.type == 'touchstart') {
            touch = getTouch(e);
        } else {
            touch = e;
        }

        //Reset
        swipeStartTime = Date.now();

        isDown = 1;

        prevent = 0; //to know when to start prevent default

        startPointX = touch.pageX;
        startPointY = touch.pageY;

        vertical_pan = false;
        horizontal_pan = false;

        $el.savedSlide = $(this); // Get the slide that has been pressed

        $el.savedSlideIndex = $el.savedSlide.index();

        //Swipe out reset
        verticalSlideFirstTimeCount = 0;
        //Reset until here

        //Turn on mousemove event when mousedown
        $(window).on('mousemove touchmove', function (e) {
            mousemove(e)
        }); //When mousedown start the handler for mousemove event

        // Clear selections so they wont affect sliding
        clearSelections();

    }

    //mousemove vars
    var savedStartPt, firstTime;

    function mousemove(e) {

        var touch;
        //Check type of event
        //Check if touch event or mousemove

        if (e.type == 'touchmove') {
            touch = getTouch(e);

            if (Math.abs(touch.pageX - startPointX) > 10) //If touch event than check if to start preventing default behavior
                prevent = 1;

            if (prevent)
                e.preventDefault();

        } else //Regular mousemove
        {
            touch = e;

            // If disabled slide & swipe out do not prevent default to let the marking of text
            if (!options.disable_slide && !options.swipe_out)
                e.preventDefault();
        }

        //Set direction of panning
        if ((-(touch.pageX - startPointX)) > 0) { //Set direction
            vars.direction = 1; //PAN LEFT
        } else {
            vars.direction = -1;
        }

        //If out boundaries than set some variables to save previous location before out boundaries
        if (anim.isOutBoundaries()) {
            if (firstTime) {
                savedStartPt = touch.pageX;

                firstTime = 0;
            }

        } else {

            if (!firstTime) { //Reset Values
                anim.currentLandPos = $el.translate3d().x;
                startPointX = touch.pageX;
            }

            firstTime = 1;

        }

        //check if to wrap
        if (verticalSlideFirstTimeCount == 1) //This will happen once every mousemove when vertical panning
        {
            // Fixing a minor issue on ie and edge
            $el.children().css("height", vars.slideHeight);

            $el.savedSlide.wrapAll("
"); //wrapAll

            verticalSlideFirstTimeCount = -1;
        }

        //Reposition according to current deltaX
        if (Math.abs(touch.pageX - startPointX) > 6) //Check to see if TAP or PAN by checking using the tap threshold (if surpassed than cancelAnimationFrame and start panning)
        {
            if (!vertical_pan && $el.end_animation) //So it will stay one direction
                horizontal_pan = true;

            window.cancelAnimationFrame(anim.slidesGlobalID); //STOP animation of sliding because if not then it will not reposition according to panning if animation hasn't ended

        }
        //Is vertical panning or horizontal panning
        if (Math.abs(touch.pageY - startPointY) > 6) //Is vertical panning
        {
            if (!horizontal_pan && $el.end_animation) {
                vertical_pan = true;
            }
        }

        //Reposition according to horizontal navigation or vertical navigation
        if (horizontal_pan) {

            if (options.disable_slide) { //Check if user disabled slide - if didn't than go to position according to distance from when horizontal panning started
                return;
            }

            vertical_pan = false;

            $el.translate3d(
                ((firstTime == 0) ? (savedStartPt - startPointX + (touch.pageX - savedStartPt) / 4) : (touch.pageX - startPointX)) //Check if out of boundaries - if true than add springy panning effect

                + anim.currentLandPos);

            //Triggers pan and changePos when swiping carousel
            $el.trigger('changePos');
            $el.trigger('pan');

        } else if (vertical_pan && options.swipe_out) { //Swipe out
            e.preventDefault();

            $(".itemslide_slideoutwrap").translate3d(0, touch.pageY - startPointY); //Using wrapper to transform brief explanation at the top.

            //Happen once...
            if (verticalSlideFirstTimeCount != -1) {
                verticalSlideFirstTimeCount = 1;
            }
        }
    } // END OF MOUSEMOVE

    function touchend(e) {
        if (isDown) {

            isDown = false;

            var touch;

            if (e.type == 'touchend') //Check for touch event or mousemove
                touch = getTouch(e);
            else
                touch = e;

            $(window).off('mousemove touchmove'); //Stop listening for the mousemove event

            //Check if vertical panning (swipe out) or horizontal panning (carousel swipe)
            //Vertical PANNING
            if (vertical_pan && options.swipe_out) {

                //HAPPENS WHEN SWIPEOUT

                vertical_pan = false; //Back to false for mousewheel (Vertical pan has finished so enable mousewheel scrolling)

                swipeOut();

                return;
            } //Veritcal Pan
            else if ($el.end_animation && !options.disable_slide) { //if finished animation of sliding and swiping is not disabled

                //Calculate deltaTime for calculation of velocity
                var deltaTime = (Date.now() - swipeStartTime);
                vars.velocity = -(touch.pageX - startPointX) / deltaTime;

                if (vars.velocity > 0) { //Set direction
                    vars.direction = 1; //PAN LEFT
                } else {
                    vars.direction = -1;
                }

                vars.distanceFromStart = (touch.pageX - startPointX) * vars.direction * -1; //Yaaa SOOO
                var landingSlideIndex = anim.getLandingSlideIndex(vars.velocity * options.swipe_sensitivity - $el.translate3d().x);

                //TAP is when deltaX is less or equal to 12px

                if (vars.distanceFromStart > 6) {
                    anim.gotoSlideByIndex(landingSlideIndex);
                    return;
                }
            } //Regular horizontal pan until here

            //TAP - click to slide
            $el.trigger({
                type: "clickSlide",
                slide: $el.savedSlideIndex
            });

            if ($el.savedSlideIndex != vars.currentIndex && !options.disable_clicktoslide) { //If this occurs then its a tap
                e.preventDefault();
                anim.gotoSlideByIndex($el.savedSlideIndex);
            }
            //TAP until here
        }

    }

};

function clearSelections() {
    /*Clear Selections*/
    if (window.getSelection) { //CLEAR SELECTIONS SO IT WONT AFFECT SLIDING
        if (window.getSelection().empty) { // Chrome
            window.getSelection().empty();
        } else if (window.getSelection().removeAllRanges) { // Firefox
            window.getSelection().removeAllRanges();
        }
    } else if (document.selection) { // IE?
        document.selection.empty();
    }
    /*Clear Selections Until Here*/
}

function getTouch(e) {
    return (($.fn.jquery == null) ? e.changedTouches[0] : (e.originalEvent.touches[0] || e.originalEvent.changedTouches[0])); //jQuery for some reason "clones" the event.
}

// EXPORT
module.exports = Navigation;
<commit_msg>Added an optional clamp to the currentLandPos to limit it's position to the horizontal borders<commit_after>// All things navigation - touch navigation and mouse
var Navigation = function (carousel, anim) {
    var $el = carousel.$el,
        options = carousel.options,
        vars = carousel.vars,
        swipeOut = carousel.swipeOut;

    // Start navigation listeners
    $el.children().on('mousedown touchstart', function (e) {
        touchstart.call(this, e);
    });
    $(window).on('mouseup touchend', function (e) {
        touchend(e);
    });

    // And the navigation functions

    // Navigation Variables
    var swipeStartTime, isDown, prevent, startPointX, startPointY, vertical_pan = false,
        horizontal_pan;

    // Swipe out Variables
    var verticalSlideFirstTimeCount;

    // Getter for vertical_pan
    this.get_vertical_pan = function () {
        return vertical_pan
    };

    function touchstart(e) {
        // no-drag feature
        if ($(e.target).attr('no-drag') === 'true' || !$el.end_animation) {
            //Or if hasn't ended swipe out escape
            return;
        }

        var touch;

        //Check for touch event or mousemove
        if (e.type == 'touchstart') {
            touch = getTouch(e);
        } else {
            touch = e;
        }

        //Reset
        swipeStartTime = Date.now();

        isDown = 1;

        prevent = 0; //to know when to start prevent default

        startPointX = touch.pageX;
        startPointY = touch.pageY;

        vertical_pan = false;
        horizontal_pan = false;

        $el.savedSlide = $(this); // Get the slide that has been pressed

        $el.savedSlideIndex = $el.savedSlide.index();

        //Swipe out reset
        verticalSlideFirstTimeCount = 0;
        //Reset until here

        //Turn on mousemove event when mousedown
        $(window).on('mousemove touchmove', function (e) {
            mousemove(e)
        }); //When mousedown start the handler for mousemove event

        // Clear selections so they wont affect sliding
        clearSelections();

    }

    //mousemove vars
    var savedStartPt, firstTime;

    function mousemove(e) {

        var touch;
        //Check type of event
        //Check if touch event or mousemove

        if (e.type == 'touchmove') {
            touch = getTouch(e);

            if (Math.abs(touch.pageX - startPointX) > 10) //If touch event than check if to start preventing default behavior
                prevent = 1;

            if (prevent)
                e.preventDefault();

        } else //Regular mousemove
        {
            touch = e;

            // If disabled slide & swipe out do not prevent default to let the marking of text
            if (!options.disable_slide && !options.swipe_out)
                e.preventDefault();
        }

        //Set direction of panning
        if ((-(touch.pageX - startPointX)) > 0) { //Set direction
            vars.direction = 1; //PAN LEFT
        } else {
            vars.direction = -1;
        }

        //If out boundaries than set some variables to save previous location before out boundaries
        if (anim.isOutBoundaries()) {
            if (firstTime) {
                savedStartPt = touch.pageX;

                firstTime = 0;
            }

        } else {

            if (!firstTime) { //Reset Values
                anim.currentLandPos = $el.translate3d().x;
                startPointX = touch.pageX;
            }

            firstTime = 1;

        }

        //check if to wrap
        if (verticalSlideFirstTimeCount == 1) //This will happen once every mousemove when vertical panning
        {
            // Fixing a minor issue on ie and edge
            $el.children().css("height", vars.slideHeight);

            $el.savedSlide.wrapAll("
"); //wrapAll

            verticalSlideFirstTimeCount = -1;
        }

        //Reposition according to current deltaX
        if (Math.abs(touch.pageX - startPointX) > 6) //Check to see if TAP or PAN by checking using the tap threshold (if surpassed than cancelAnimationFrame and start panning)
        {
            if (!vertical_pan && $el.end_animation) //So it will stay one direction
                horizontal_pan = true;

            window.cancelAnimationFrame(anim.slidesGlobalID); //STOP animation of sliding because if not then it will not reposition according to panning if animation hasn't ended

        }
        //Is vertical panning or horizontal panning
        if (Math.abs(touch.pageY - startPointY) > 6) //Is vertical panning
        {
            if (!horizontal_pan && $el.end_animation) {
                vertical_pan = true;
            }
        }

        //Reposition according to horizontal navigation or vertical navigation
        if (horizontal_pan) {

            if (options.disable_slide) { //Check if user disabled slide - if didn't than go to position according to distance from when horizontal panning started
                return;
            }

            if (options.snap_borders) {
            	anim.currentLandPos = clamp( -(vars.allSlidesWidth - $el.parent().width()), 0, anim.currentLandPos);
            }

            vertical_pan = false;

            $el.translate3d(
                ((firstTime == 0) ? (savedStartPt - startPointX + (touch.pageX - savedStartPt) / 4) : (touch.pageX - startPointX)) //Check if out of boundaries - if true than add springy panning effect

                + anim.currentLandPos);

            //Triggers pan and changePos when swiping carousel
            $el.trigger('changePos');
            $el.trigger('pan');

        } else if (vertical_pan && options.swipe_out) { //Swipe out
            e.preventDefault();

            $(".itemslide_slideoutwrap").translate3d(0, touch.pageY - startPointY); //Using wrapper to transform brief explanation at the top.

            //Happen once...
            if (verticalSlideFirstTimeCount != -1) {
                verticalSlideFirstTimeCount = 1;
            }
        }
    } // END OF MOUSEMOVE

    function touchend(e) {
        if (isDown) {

            isDown = false;

            var touch;

            if (e.type == 'touchend') //Check for touch event or mousemove
                touch = getTouch(e);
            else
                touch = e;

            $(window).off('mousemove touchmove'); //Stop listening for the mousemove event

            //Check if vertical panning (swipe out) or horizontal panning (carousel swipe)
            //Vertical PANNING
            if (vertical_pan && options.swipe_out) {

                //HAPPENS WHEN SWIPEOUT

                vertical_pan = false; //Back to false for mousewheel (Vertical pan has finished so enable mousewheel scrolling)

                swipeOut();

                return;
            } //Veritcal Pan
            else if ($el.end_animation && !options.disable_slide) { //if finished animation of sliding and swiping is not disabled

                //Calculate deltaTime for calculation of velocity
                var deltaTime = (Date.now() - swipeStartTime);
                vars.velocity = -(touch.pageX - startPointX) / deltaTime;

                if (vars.velocity > 0) { //Set direction
                    vars.direction = 1; //PAN LEFT
                } else {
                    vars.direction = -1;
                }

                vars.distanceFromStart = (touch.pageX - startPointX) * vars.direction * -1; //Yaaa SOOO
                var landingSlideIndex = anim.getLandingSlideIndex(vars.velocity * options.swipe_sensitivity - $el.translate3d().x);

                //TAP is when deltaX is less or equal to 12px

                if (vars.distanceFromStart > 6) {
                    anim.gotoSlideByIndex(landingSlideIndex);
                    return;
                }
            } //Regular horizontal pan until here

            //TAP - click to slide
            $el.trigger({
                type: "clickSlide",
                slide: $el.savedSlideIndex
            });

            if ($el.savedSlideIndex != vars.currentIndex && !options.disable_clicktoslide) { //If this occurs then its a tap
                e.preventDefault();
                anim.gotoSlideByIndex($el.savedSlideIndex);
            }
            //TAP until here
        }

    }

};

function clearSelections() {
    /*Clear Selections*/
    if (window.getSelection) { //CLEAR SELECTIONS SO IT WONT AFFECT SLIDING
        if (window.getSelection().empty) { // Chrome
            window.getSelection().empty();
        } else if (window.getSelection().removeAllRanges) { // Firefox
            window.getSelection().removeAllRanges();
        }
    } else if (document.selection) { // IE?
        document.selection.empty();
    }
    /*Clear Selections Until Here*/
}

function getTouch(e) {
    return (($.fn.jquery == null) ? e.changedTouches[0] : (e.originalEvent.touches[0] || e.originalEvent.changedTouches[0])); //jQuery for some reason "clones" the event.
}

// EXPORT
module.exports = Navigation;
<|endoftext|>


<commit_before>/* ng-FitText.js v3.3.3
 * https://github.com/patrickmarabeas/ng-FitText.js
 *
 * Original jQuery project: https://github.com/davatron5000/FitText.js
 *
 * Copyright 2015, Patrick Marabeas http://marabeas.io
 * Released under the MIT license
 * http://opensource.org/licenses/mit-license.php
 *
 * Date: 06/05/2015
 */

(function(window, document, angular, undefined) {

  'use strict';

  angular.module('ngFitText', [])
    .value( 'config', {
      'debounce': false,
      'delay': 250,
      'loadDelay': 10,
      'min': undefined,
      'max': undefined
    })

    .directive('fittext', ['$timeout', 'config', 'fitTextConfig', function($timeout, config, fitTextConfig) {
      return {
        restrict: 'A',
        scope: true,
        link: function(scope, element, attrs) {
          angular.extend(config, fitTextConfig.config);

          element[0].style.display = 'inline-block';
          element[0].style.lineHeight = '1';

          var parent = element.parent();
          var compressor = attrs.fittext || 1;
          var loadDelay = attrs.fittextLoadDelay || config.loadDelay;
          var nl = element[0].querySelectorAll('[fittext-nl],[data-fittext-nl]').length || 1;
          var minFontSize = attrs.fittextMin || config.min || Number.NEGATIVE_INFINITY;
          var maxFontSize = attrs.fittextMax || config.max || Number.POSITIVE_INFINITY;

          var resizer = function() {
            element[0].style.fontSize = '10px';
            var ratio = element[0].offsetHeight / element[0].offsetWidth / nl;
            element[0].style.fontSize = Math.max(
              Math.min((parent[0].offsetWidth - 6) * ratio * compressor,
                parseFloat(maxFontSize)
              ),
              parseFloat(minFontSize)
            ) + 'px';
          };

          $timeout( function() { resizer() }, loadDelay);

          scope.$watch(attrs.ngModel, function() { resizer() });

          config.debounce
            ? angular.element(window).bind('resize', config.debounce(function(){ scope.$apply(resizer)}, config.delay))
            : angular.element(window).bind('resize', function(){ scope.$apply(resizer)});
        }
      }
    }])

    .provider('fitTextConfig', function() {
      var self = this;
      this.config = {};
      this.$get = function() {
        var extend = {};
        extend.config = self.config;
        return extend;
      };
      return this;
    });

})(window, document, angular);<commit_msg>Prevent invalid ratio calculation and fitting<commit_after>/* ng-FitText.js v3.3.3
 * https://github.com/patrickmarabeas/ng-FitText.js
 *
 * Original jQuery project: https://github.com/davatron5000/FitText.js
 *
 * Copyright 2015, Patrick Marabeas http://marabeas.io
 * Released under the MIT license
 * http://opensource.org/licenses/mit-license.php
 *
 * Date: 06/05/2015
 */

(function(window, document, angular, undefined) {

  'use strict';

  angular.module('ngFitText', [])
    .value( 'config', {
      'debounce': false,
      'delay': 250,
      'loadDelay': 10,
      'min': undefined,
      'max': undefined
    })

    .directive('fittext', ['$timeout', 'config', 'fitTextConfig', function($timeout, config, fitTextConfig) {
      return {
        restrict: 'A',
        scope: true,
        link: function(scope, element, attrs) {
          angular.extend(config, fitTextConfig.config);

          element[0].style.display = 'inline-block';
          element[0].style.lineHeight = '1';

          var parent = element.parent();
          var compressor = attrs.fittext || 1;
          var loadDelay = attrs.fittextLoadDelay || config.loadDelay;
          var nl = element[0].querySelectorAll('[fittext-nl],[data-fittext-nl]').length || 1;
          var minFontSize = attrs.fittextMin || config.min || Number.NEGATIVE_INFINITY;
          var maxFontSize = attrs.fittextMax || config.max || Number.POSITIVE_INFINITY;

          var resizer = function() {
            if ( element[0].offsetHeight * element[0].offsetWidth === 0 ) {
              // Skip setting the font size if the element height or width
              // have been set to 0 (or auto) for any reason.
              return;
            }

            element[0].style.fontSize = '10px';
            var ratio = element[0].offsetHeight / element[0].offsetWidth / nl;
            element[0].style.fontSize = Math.max(
              Math.min((parent[0].offsetWidth - 6) * ratio * compressor,
                parseFloat(maxFontSize)
              ),
              parseFloat(minFontSize)
            ) + 'px';
          };

          $timeout( function() { resizer() }, loadDelay);

          scope.$watch(attrs.ngModel, function() { resizer() });

          config.debounce
            ? angular.element(window).bind('resize', config.debounce(function(){ scope.$apply(resizer)}, config.delay))
            : angular.element(window).bind('resize', function(){ scope.$apply(resizer)});
        }
      }
    }])

    .provider('fitTextConfig', function() {
      var self = this;
      this.config = {};
      this.$get = function() {
        var extend = {};
        extend.config = self.config;
        return extend;
      };
      return this;
    });

})(window, document, angular);<|endoftext|>


<commit_before>/* application entry point */

// require mithril globally for convenience
window.m = require('mithril');
// for moment a global object makes loading locales easier
window.moment = require('moment');

if (window.lichess.mode === 'dev') {
  var Q = require('q');
  Q.longStackSupport = true;
  Q.onerror = function(err) { console.error(err, err.stack); };
}

var utils = require('./utils');
var session = require('./session');
var i18n = require('./i18n');
var xhr = require('./xhr');
var backbutton = require('./backbutton');
var menu = require('./ui/menu');
var storage = require('./storage');

var home = require('./ui/home');
var play = require('./ui/play');
var seek = require('./ui/seek');
var seeks = require('./ui/seeks');
var otb = require('./ui/otb/main');
var ai = require('./ui/ai/main');
var settingsUi = require('./ui/settings');
var boardThemes = require('./ui/settings/boardThemes');
var pieceThemes = require('./ui/settings/pieceThemes');

var triedToLogin = false;

var refreshInterval = 60000;
var refreshIntervalID;

function main() {

  m.route(document.body, '/', {
    '/': home,
    '/seeks': seeks,
    '/seek': seek,
    '/otb': otb,
    '/ai': ai,
    '/play/:id': play,
    '/settings': settingsUi,
    '/settings/themes/board': boardThemes,
    '/settings/themes/piece': pieceThemes
  });

  // pull session data once (to log in user automatically thanks to cookie)
  // and also listen to online event in case network was disconnected at app
  // startup
  if (utils.hasNetwork())
    onOnline();
  else {
    window.navigator.notification.alert(i18n('noInternetConnection'));
    menu.open();
    m.redraw();
  }

  document.addEventListener('online', onOnline, false);

  // if connected, refresh data every min, and on resume
  refreshIntervalID = setInterval(refresh, refreshInterval);
  document.addEventListener('resume', onResume, false);
  document.addEventListener('pause', onPause, false);

  // iOs keyboard hack
  // TODO we may want to remove this and call only on purpose
  window.cordova.plugins.Keyboard.disableScroll(true);

  if (window.lichess.gaId) window.analytics.startTrackerWithId(window.lichess.gaId);

  document.addEventListener('backbutton', backbutton, false);

  setTimeout(function() {
    window.navigator.splashscreen.hide();
    xhr.status();
  }, 500);
}

function refresh() {
  if (utils.hasNetwork() && session.isConnected()) session.refresh();
}

function onResume() {
  refresh();
  refreshIntervalID = setInterval(refresh, refreshInterval);
}

function onPause() {
  clearInterval(refreshIntervalID);
}

function onOnline() {
  session.rememberLogin().then(function() {
    if (/^\/$/.test(m.route()) && !triedToLogin) {
      triedToLogin = true;
      var nowPlaying = session.nowPlaying();
      if (nowPlaying.length)
        m.route('/play/' + nowPlaying[0].fullId);
      else
        window.plugins.toast.show(i18n('connectedToLichess'), 'short', 'center');
    }
  }, function(err) {
    if (/^\/$/.test(m.route()) && !triedToLogin) {
      // means user is anonymous here
      if (err.message === 'unauthorizedError') {
        triedToLogin = true;
        var lastPlayedAnon = storage.get('lastPlayedGameURLAsAnon');
        if (lastPlayedAnon)
          m.route('/play' + lastPlayedAnon);
        else {
          window.plugins.toast.show(i18n('connectedToLichess'), 'short', 'center');
          menu.open();
          m.redraw();
        }
      }
    }
  });
}

function handleError(event, source, fileno, columnNumber) {
  var description = event + ' at ' + source + ' [' + fileno + ', ' + columnNumber + ']';
  window.analytics.trackException(description, true);
}

window.onerror = handleError;

window.handleOpenURL = function(url) {
  setTimeout(function() {
    var gameId = url.match(/^lichess:\/\/(\w+)/)[1];
    if (gameId) m.route('/play/' + gameId);
  }, 0);
};

document.addEventListener('deviceready',
  // i18n must be loaded before any rendering happens
  utils.f(i18n.loadPreferredLanguage, main),
  false
);
<commit_msg>Add also app version in error description<commit_after>/* application entry point */

// require mithril globally for convenience
window.m = require('mithril');
// for moment a global object makes loading locales easier
window.moment = require('moment');

if (window.lichess.mode === 'dev') {
  var Q = require('q');
  Q.longStackSupport = true;
  Q.onerror = function(err) { console.error(err, err.stack); };
}

var utils = require('./utils');
var session = require('./session');
var i18n = require('./i18n');
var xhr = require('./xhr');
var backbutton = require('./backbutton');
var menu = require('./ui/menu');
var storage = require('./storage');

var home = require('./ui/home');
var play = require('./ui/play');
var seek = require('./ui/seek');
var seeks = require('./ui/seeks');
var otb = require('./ui/otb/main');
var ai = require('./ui/ai/main');
var settingsUi = require('./ui/settings');
var boardThemes = require('./ui/settings/boardThemes');
var pieceThemes = require('./ui/settings/pieceThemes');

var triedToLogin = false;

var refreshInterval = 60000;
var refreshIntervalID;

function main() {

  m.route(document.body, '/', {
    '/': home,
    '/seeks': seeks,
    '/seek': seek,
    '/otb': otb,
    '/ai': ai,
    '/play/:id': play,
    '/settings': settingsUi,
    '/settings/themes/board': boardThemes,
    '/settings/themes/piece': pieceThemes
  });

  // pull session data once (to log in user automatically thanks to cookie)
  // and also listen to online event in case network was disconnected at app
  // startup
  if (utils.hasNetwork())
    onOnline();
  else {
    window.navigator.notification.alert(i18n('noInternetConnection'));
    menu.open();
    m.redraw();
  }

  document.addEventListener('online', onOnline, false);

  // if connected, refresh data every min, and on resume
  refreshIntervalID = setInterval(refresh, refreshInterval);
  document.addEventListener('resume', onResume, false);
  document.addEventListener('pause', onPause, false);

  // iOs keyboard hack
  // TODO we may want to remove this and call only on purpose
  window.cordova.plugins.Keyboard.disableScroll(true);

  if (window.lichess.gaId) window.analytics.startTrackerWithId(window.lichess.gaId);

  document.addEventListener('backbutton', backbutton, false);

  setTimeout(function() {
    window.navigator.splashscreen.hide();
    xhr.status();
  }, 500);
}

function refresh() {
  if (utils.hasNetwork() && session.isConnected()) session.refresh();
}

function onResume() {
  refresh();
  refreshIntervalID = setInterval(refresh, refreshInterval);
}

function onPause() {
  clearInterval(refreshIntervalID);
}

function onOnline() {
  session.rememberLogin().then(function() {
    if (/^\/$/.test(m.route()) && !triedToLogin) {
      triedToLogin = true;
      var nowPlaying = session.nowPlaying();
      if (nowPlaying.length)
        m.route('/play/' + nowPlaying[0].fullId);
      else
        window.plugins.toast.show(i18n('connectedToLichess'), 'short', 'center');
    }
  }, function(err) {
    if (/^\/$/.test(m.route()) && !triedToLogin) {
      // means user is anonymous here
      if (err.message === 'unauthorizedError') {
        triedToLogin = true;
        var lastPlayedAnon = storage.get('lastPlayedGameURLAsAnon');
        if (lastPlayedAnon)
          m.route('/play' + lastPlayedAnon);
        else {
          window.plugins.toast.show(i18n('connectedToLichess'), 'short', 'center');
          menu.open();
          m.redraw();
        }
      }
    }
  });
}

function handleError(event, source, fileno, columnNumber) {
  var description = '[' + window.lichess.version + ']' + event + ' at ' + source + ' [' + fileno + ', ' + columnNumber + ']';
  window.analytics.trackException(description, true);
}

window.onerror = handleError;

window.handleOpenURL = function(url) {
  setTimeout(function() {
    var gameId = url.match(/^lichess:\/\/(\w+)/)[1];
    if (gameId) m.route('/play/' + gameId);
  }, 0);
};

document.addEventListener('deviceready',
  // i18n must be loaded before any rendering happens
  utils.f(i18n.loadPreferredLanguage, main),
  false
);
<|endoftext|>