notes infinite scroll
This commit is contained in:
@@ -32,6 +32,11 @@ angular.module('app.frontend')
|
||||
|
||||
var isFirstLoad = true;
|
||||
|
||||
this.notesToDisplay = 20;
|
||||
this.paginate = function() {
|
||||
this.notesToDisplay += 20
|
||||
}
|
||||
|
||||
this.tagDidChange = function(tag, oldTag) {
|
||||
this.showMenu = false;
|
||||
|
||||
|
||||
@@ -167,82 +167,6 @@ angular.module('app.frontend')
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
Import
|
||||
*/
|
||||
|
||||
this.importJSONData = function(data, password, callback) {
|
||||
console.log("Importing data", data);
|
||||
|
||||
var onDataReady = function() {
|
||||
var items = modelManager.mapResponseItemsToLocalModels(data.items);
|
||||
items.forEach(function(item){
|
||||
item.setDirty(true);
|
||||
item.markAllReferencesDirty();
|
||||
})
|
||||
this.syncWithOptions(callback, {additionalFields: ["created_at", "updated_at"]});
|
||||
}.bind(this)
|
||||
|
||||
if(data.auth_params) {
|
||||
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, data.auth_params), function(keys){
|
||||
var mk = keys.mk;
|
||||
try {
|
||||
this.decryptItemsWithKey(data.items, mk);
|
||||
// delete items enc_item_key since the user's actually key will do the encrypting once its passed off
|
||||
data.items.forEach(function(item){
|
||||
item.enc_item_key = null;
|
||||
item.auth_hash = null;
|
||||
})
|
||||
onDataReady();
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Error decrypting", e);
|
||||
alert("There was an error decrypting your items. Make sure the password you entered is correct and try again.");
|
||||
callback(false, null);
|
||||
return;
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
onDataReady();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Export
|
||||
*/
|
||||
|
||||
this.itemsDataFile = function(ek) {
|
||||
var textFile = null;
|
||||
var makeTextFile = function (text) {
|
||||
var data = new Blob([text], {type: 'text/json'});
|
||||
|
||||
// If we are replacing a previously generated file we need to
|
||||
// manually revoke the object URL to avoid memory leaks.
|
||||
if (textFile !== null) {
|
||||
window.URL.revokeObjectURL(textFile);
|
||||
}
|
||||
|
||||
textFile = window.URL.createObjectURL(data);
|
||||
|
||||
// returns a URL you can use as a href
|
||||
return textFile;
|
||||
}.bind(this);
|
||||
|
||||
var items = _.map(modelManager.allItemsMatchingTypes(["Tag", "Note"]), function(item){
|
||||
var itemParams = new ItemParams(item, ek);
|
||||
return itemParams.paramsForExportFile();
|
||||
}.bind(this));
|
||||
|
||||
var data = {
|
||||
items: items
|
||||
}
|
||||
|
||||
// auth params are only needed when encrypted with a standard file key
|
||||
data["auth_params"] = this.getAuthParams();
|
||||
|
||||
return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */));
|
||||
}
|
||||
|
||||
this.staticifyObject = function(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
angular.module('app.frontend').directive('infiniteScroll', [
|
||||
'$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) {
|
||||
return {
|
||||
link: function(scope, elem, attrs) {
|
||||
elem.css('overflow-x', 'hidden');
|
||||
elem.css('height', 'inherit');
|
||||
|
||||
var offset = parseInt(attrs.threshold) || 0;
|
||||
var e = elem[0]
|
||||
|
||||
elem.on('scroll', function(){
|
||||
if(scope.$eval(attrs.canLoad) && e.scrollTop + e.offsetHeight >= e.scrollHeight - offset) {
|
||||
scope.$apply(attrs.infiniteScroll);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
@@ -85,7 +85,6 @@ class AccountMenu {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/* Import/Export */
|
||||
|
||||
$scope.archiveFormData = {encrypted: $scope.user ? true : false};
|
||||
@@ -97,16 +96,20 @@ class AccountMenu {
|
||||
|
||||
var ek = $scope.archiveFormData.encrypted ? syncManager.masterKey : null;
|
||||
|
||||
link.href = authManager.itemsDataFile(ek);
|
||||
link.href = $scope.itemsDataFile(ek);
|
||||
link.click();
|
||||
}
|
||||
|
||||
$scope.submitImportPassword = function() {
|
||||
$scope.performImport($scope.importData.data, $scope.importData.password);
|
||||
}
|
||||
|
||||
$scope.performImport = function(data, password) {
|
||||
$scope.importData.loading = true;
|
||||
// allow loading indicator to come up with timeout
|
||||
$timeout(function(){
|
||||
authManager.importJSONData(data, password, function(success, response){
|
||||
console.log("Import response:", success, response);
|
||||
$scope.importJSONData(data, password, function(success, response){
|
||||
// console.log("Import response:", success, response);
|
||||
$scope.importData.loading = false;
|
||||
if(success) {
|
||||
$scope.importData = null;
|
||||
@@ -117,10 +120,6 @@ class AccountMenu {
|
||||
})
|
||||
}
|
||||
|
||||
$scope.submitImportPassword = function() {
|
||||
$scope.performImport($scope.importData.data, $scope.importData.password);
|
||||
}
|
||||
|
||||
$scope.importFileSelected = function(files) {
|
||||
$scope.importData = {};
|
||||
|
||||
@@ -147,6 +146,79 @@ class AccountMenu {
|
||||
return allNotes.length + "/" + allNotes.length + " notes encrypted";
|
||||
}
|
||||
|
||||
$scope.importJSONData = function(data, password, callback) {
|
||||
console.log("Importing data", data);
|
||||
|
||||
var onDataReady = function() {
|
||||
var items = modelManager.mapResponseItemsToLocalModels(data.items);
|
||||
items.forEach(function(item){
|
||||
item.setDirty(true);
|
||||
item.markAllReferencesDirty();
|
||||
})
|
||||
|
||||
syncManager.sync(callback, {additionalFields: ["created_at", "updated_at"]});
|
||||
}.bind(this)
|
||||
|
||||
if(data.auth_params) {
|
||||
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, data.auth_params), function(keys){
|
||||
var mk = keys.mk;
|
||||
try {
|
||||
EncryptionHelper.decryptMultipleItems(data.items, mk);
|
||||
// delete items enc_item_key since the user's actually key will do the encrypting once its passed off
|
||||
data.items.forEach(function(item){
|
||||
item.enc_item_key = null;
|
||||
item.auth_hash = null;
|
||||
})
|
||||
onDataReady();
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Error decrypting", e);
|
||||
alert("There was an error decrypting your items. Make sure the password you entered is correct and try again.");
|
||||
callback(false, null);
|
||||
return;
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
onDataReady();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Export
|
||||
*/
|
||||
|
||||
$scope.itemsDataFile = function(ek) {
|
||||
var textFile = null;
|
||||
var makeTextFile = function (text) {
|
||||
var data = new Blob([text], {type: 'text/json'});
|
||||
|
||||
// If we are replacing a previously generated file we need to
|
||||
// manually revoke the object URL to avoid memory leaks.
|
||||
if (textFile !== null) {
|
||||
window.URL.revokeObjectURL(textFile);
|
||||
}
|
||||
|
||||
textFile = window.URL.createObjectURL(data);
|
||||
|
||||
// returns a URL you can use as a href
|
||||
return textFile;
|
||||
}.bind(this);
|
||||
|
||||
var items = _.map(modelManager.allItemsMatchingTypes(["Tag", "Note"]), function(item){
|
||||
var itemParams = new ItemParams(item, ek);
|
||||
return itemParams.paramsForExportFile();
|
||||
}.bind(this));
|
||||
|
||||
var data = {
|
||||
items: items
|
||||
}
|
||||
|
||||
// auth params are only needed when encrypted with a standard file key
|
||||
data["auth_params"] = authManager.getAuthParams();
|
||||
|
||||
return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6
app/assets/javascripts/app/services/filters/startFrom.js
Normal file
6
app/assets/javascripts/app/services/filters/startFrom.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// Start from filter
|
||||
angular.module('app.frontend').filter('startFrom', function() {
|
||||
return function(input, start) {
|
||||
return input.slice(start);
|
||||
};
|
||||
});
|
||||
@@ -76,7 +76,7 @@ class ModelManager {
|
||||
|
||||
this.notifySyncObserversOfModels(models);
|
||||
|
||||
this.sortItems();
|
||||
// this.sortItems();
|
||||
return models;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,19 @@ class SyncManager {
|
||||
return this.serverURL + "/items/sync";
|
||||
}
|
||||
|
||||
set syncToken(token) {
|
||||
console.log("setting token", token);
|
||||
this._syncToken = token;
|
||||
localStorage.setItem("syncToken", token);
|
||||
}
|
||||
|
||||
get syncToken() {
|
||||
if(!this._syncToken) {
|
||||
this._syncToken = localStorage.getItem("syncToken");
|
||||
}
|
||||
return this._syncToken;
|
||||
}
|
||||
|
||||
sync(callback, options = {}) {
|
||||
|
||||
if(this.syncStatus.syncOpInProgress) {
|
||||
@@ -75,6 +88,8 @@ class SyncManager {
|
||||
|
||||
var allDirtyItems = this.modelManager.getDirtyItems();
|
||||
|
||||
console.log("Syncing dirty items", allDirtyItems);
|
||||
|
||||
// we want to write all dirty items to disk only if the user is offline, or if the sync op fails
|
||||
// if the sync op succeeds, these items will be written to disk by handling the "saved_items" response from the server
|
||||
if(this.authManager.offline()) {
|
||||
@@ -113,10 +128,13 @@ class SyncManager {
|
||||
request.sync_token = this.syncToken;
|
||||
request.cursor_token = this.cursorToken;
|
||||
|
||||
console.log("Syncing with token", request.sync_token, request.cursor_token);
|
||||
|
||||
request.post().then(function(response) {
|
||||
console.log("Sync completion", response.plain());
|
||||
|
||||
this.modelManager.clearDirtyItems(subItems);
|
||||
this.syncStatus.error = null;
|
||||
this.syncToken = response.sync_token;
|
||||
this.cursorToken = response.cursor_token;
|
||||
|
||||
this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
|
||||
@@ -134,8 +152,13 @@ class SyncManager {
|
||||
this.syncStatus.syncOpInProgress = false;
|
||||
this.syncStatus.current += subItems.length;
|
||||
|
||||
// set the sync token at the end, so that if any errors happen above, you can resync
|
||||
this.syncToken = response.sync_token;
|
||||
|
||||
if(this.cursorToken || this.repeatOnCompletion || this.needsMoreSync) {
|
||||
this.sync(callback, options);
|
||||
setTimeout(function () {
|
||||
this.sync(callback, options);
|
||||
}.bind(this), 10); // wait 10ms to allow UI to update
|
||||
} else {
|
||||
if(callback) {
|
||||
callback(response);
|
||||
|
||||
Reference in New Issue
Block a user