338 lines
10 KiB
JavaScript
338 lines
10 KiB
JavaScript
angular.module('app')
|
|
.controller('HomeCtrl', function ($scope, $location, $rootScope, $timeout, modelManager,
|
|
dbManager, syncManager, authManager, themeManager, passcodeManager, storageManager, migrationManager,
|
|
privilegesManager, statusManager) {
|
|
|
|
storageManager.initialize(passcodeManager.hasPasscode(), authManager.isEphemeralSession());
|
|
|
|
$scope.platform = getPlatformString();
|
|
|
|
$scope.onUpdateAvailable = function(version) {
|
|
$rootScope.$broadcast('new-update-available', version);
|
|
}
|
|
|
|
$rootScope.$on("panel-resized", (event, info) => {
|
|
if(info.panel == "notes") { this.notesCollapsed = info.collapsed; }
|
|
if(info.panel == "tags") { this.tagsCollapsed = info.collapsed; }
|
|
|
|
let appClass = "";
|
|
if(this.notesCollapsed) { appClass += "collapsed-notes"; }
|
|
if(this.tagsCollapsed) { appClass += " collapsed-tags"; }
|
|
|
|
$scope.appClass = appClass;
|
|
})
|
|
|
|
/* Used to avoid circular dependencies where syncManager cannot be imported but rootScope can */
|
|
$rootScope.sync = function(source) {
|
|
syncManager.sync();
|
|
}
|
|
|
|
$rootScope.lockApplication = function() {
|
|
// Reloading wipes current objects from memory
|
|
window.location.reload();
|
|
}
|
|
|
|
const initiateSync = () => {
|
|
authManager.loadInitialData();
|
|
|
|
this.syncStatusObserver = syncManager.registerSyncStatusObserver((status) => {
|
|
if(status.retrievedCount > 20) {
|
|
var text = `Downloading ${status.retrievedCount} items. Keep app open.`
|
|
this.syncStatus = statusManager.replaceStatusWithString(this.syncStatus, text);
|
|
this.showingDownloadStatus = true;
|
|
} else if(this.showingDownloadStatus) {
|
|
this.showingDownloadStatus = false;
|
|
var text = "Download Complete.";
|
|
this.syncStatus = statusManager.replaceStatusWithString(this.syncStatus, text);
|
|
setTimeout(() => {
|
|
this.syncStatus = statusManager.removeStatus(this.syncStatus);
|
|
}, 2000);
|
|
}
|
|
})
|
|
|
|
syncManager.setKeyRequestHandler(async () => {
|
|
let offline = authManager.offline();
|
|
let auth_params = offline ? passcodeManager.passcodeAuthParams() : await authManager.getAuthParams();
|
|
let keys = offline ? passcodeManager.keys() : await authManager.keys();
|
|
return {
|
|
keys: keys,
|
|
offline: offline,
|
|
auth_params: auth_params
|
|
}
|
|
});
|
|
|
|
let lastSessionInvalidAlert;
|
|
|
|
syncManager.addEventHandler((syncEvent, data) => {
|
|
$rootScope.$broadcast(syncEvent, data || {});
|
|
if(syncEvent == "sync-session-invalid") {
|
|
// On Windows, some users experience issues where this message keeps appearing. It might be that on focus, the app syncs, and this message triggers again.
|
|
// We'll only show it once every X seconds
|
|
let showInterval = 30; // At most 30 seconds in between
|
|
if(!lastSessionInvalidAlert || (new Date() - lastSessionInvalidAlert)/1000 > showInterval) {
|
|
lastSessionInvalidAlert = new Date();
|
|
setTimeout(function () {
|
|
// If this alert is displayed on launch, it may sometimes dismiss automatically really quicky for some reason. So we wrap in timeout
|
|
alert("Your session has expired. New changes will not be pulled in. Please sign out and sign back in to refresh your session.");
|
|
}, 500);
|
|
}
|
|
} else if(syncEvent == "sync-exception") {
|
|
alert(`There was an error while trying to save your items. Please contact support and share this message: ${data}`);
|
|
}
|
|
});
|
|
|
|
let encryptionEnabled = authManager.user || passcodeManager.hasPasscode();
|
|
this.syncStatus = statusManager.addStatusFromString(encryptionEnabled ? "Decrypting items..." : "Loading items...");
|
|
|
|
let incrementalCallback = (current, total) => {
|
|
let notesString = `${current}/${total} items...`
|
|
this.syncStatus = statusManager.replaceStatusWithString(this.syncStatus, encryptionEnabled ? `Decrypting ${notesString}` : `Loading ${notesString}`);
|
|
}
|
|
|
|
syncManager.loadLocalItems(incrementalCallback).then(() => {
|
|
$timeout(() => {
|
|
$rootScope.$broadcast("initial-data-loaded"); // This needs to be processed first before sync is called so that singletonManager observers function properly.
|
|
// Perform integrity check on first sync
|
|
this.syncStatus = statusManager.replaceStatusWithString(this.syncStatus, "Syncing...");
|
|
syncManager.sync({performIntegrityCheck: true}).then(() => {
|
|
this.syncStatus = statusManager.removeStatus(this.syncStatus);
|
|
})
|
|
// refresh every 30s
|
|
setInterval(function () {
|
|
syncManager.sync();
|
|
}, 30000);
|
|
})
|
|
|
|
});
|
|
|
|
authManager.addEventHandler((event) => {
|
|
if(event == SFAuthManager.DidSignOutEvent) {
|
|
modelManager.handleSignout();
|
|
syncManager.handleSignout();
|
|
}
|
|
})
|
|
}
|
|
|
|
function load() {
|
|
// pass keys to storageManager to decrypt storage
|
|
// Update: Wait, why? passcodeManager already handles this.
|
|
// storageManager.setKeys(passcodeManager.keys());
|
|
|
|
openDatabase();
|
|
// Retrieve local data and begin sycing timer
|
|
initiateSync();
|
|
}
|
|
|
|
if(passcodeManager.isLocked()) {
|
|
$scope.needsUnlock = true;
|
|
} else {
|
|
load();
|
|
}
|
|
|
|
$scope.onSuccessfulUnlock = function() {
|
|
$timeout(() => {
|
|
$scope.needsUnlock = false;
|
|
load();
|
|
})
|
|
}
|
|
|
|
function openDatabase() {
|
|
dbManager.setLocked(false);
|
|
dbManager.openDatabase(null, function() {
|
|
// new database, delete syncToken so that items can be refetched entirely from server
|
|
syncManager.clearSyncToken();
|
|
syncManager.sync();
|
|
})
|
|
}
|
|
|
|
/*
|
|
Editor Callbacks
|
|
*/
|
|
|
|
$scope.updateTagsForNote = function(note, stringTags) {
|
|
var toRemove = [];
|
|
for(var tag of note.tags) {
|
|
if(stringTags.indexOf(tag.title) === -1) {
|
|
// remove this tag
|
|
toRemove.push(tag);
|
|
}
|
|
}
|
|
|
|
for(var tagToRemove of toRemove) {
|
|
tagToRemove.removeItemAsRelationship(note);
|
|
tagToRemove.setDirty(true);
|
|
}
|
|
|
|
var tags = [];
|
|
for(var tagString of stringTags) {
|
|
var existingRelationship = _.find(note.tags, {title: tagString});
|
|
if(!existingRelationship) {
|
|
tags.push(modelManager.findOrCreateTagByTitle(tagString));
|
|
}
|
|
}
|
|
|
|
for(var tag of tags) {
|
|
tag.addItemAsRelationship(note);
|
|
tag.setDirty(true);
|
|
}
|
|
|
|
syncManager.sync();
|
|
}
|
|
|
|
/*
|
|
Tags Ctrl Callbacks
|
|
*/
|
|
|
|
$scope.tagsSelectionMade = function(tag) {
|
|
// If a tag is selected twice, then the needed dummy note is removed.
|
|
// So we perform this check.
|
|
if($scope.selectedTag && tag && $scope.selectedTag.uuid == tag.uuid) {
|
|
return;
|
|
}
|
|
|
|
if($scope.selectedNote && $scope.selectedNote.dummy) {
|
|
modelManager.removeItemLocally($scope.selectedNote);
|
|
$scope.selectedNote = null;
|
|
}
|
|
|
|
$scope.selectedTag = tag;
|
|
}
|
|
|
|
$scope.tagsAddNew = function(tag) {
|
|
modelManager.addItem(tag);
|
|
}
|
|
|
|
$scope.tagsSave = function(tag, callback) {
|
|
if(!tag.title || tag.title.length == 0) {
|
|
$scope.removeTag(tag);
|
|
return;
|
|
}
|
|
tag.setDirty(true);
|
|
syncManager.sync().then(callback);
|
|
modelManager.resortTag(tag);
|
|
}
|
|
|
|
/*
|
|
Notes Ctrl Callbacks
|
|
*/
|
|
|
|
$scope.removeTag = function(tag) {
|
|
if(confirm("Are you sure you want to delete this tag? Note: deleting a tag will not delete its notes.")) {
|
|
modelManager.setItemToBeDeleted(tag);
|
|
syncManager.sync().then(() => {
|
|
// force scope tags to update on sub directives
|
|
$rootScope.safeApply();
|
|
});
|
|
}
|
|
}
|
|
|
|
$scope.notesSelectionMade = function(note) {
|
|
$scope.selectedNote = note;
|
|
}
|
|
|
|
$scope.notesAddNew = function(note) {
|
|
modelManager.addItem(note);
|
|
|
|
if(!$scope.selectedTag.isSmartTag()) {
|
|
$scope.selectedTag.addItemAsRelationship(note);
|
|
$scope.selectedTag.setDirty(true);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Shared Callbacks
|
|
*/
|
|
|
|
$rootScope.safeApply = function(fn) {
|
|
var phase = this.$root.$$phase;
|
|
if(phase == '$apply' || phase == '$digest')
|
|
this.$eval(fn);
|
|
else
|
|
this.$apply(fn);
|
|
};
|
|
|
|
$rootScope.notifyDelete = function() {
|
|
$timeout(function() {
|
|
$rootScope.$broadcast("noteDeleted");
|
|
}.bind(this), 0);
|
|
}
|
|
|
|
$scope.deleteNote = function(note) {
|
|
modelManager.setItemToBeDeleted(note);
|
|
|
|
if(note == $scope.selectedNote) {
|
|
$scope.selectedNote = null;
|
|
}
|
|
|
|
if(note.dummy) {
|
|
modelManager.removeItemLocally(note);
|
|
$rootScope.notifyDelete();
|
|
return;
|
|
}
|
|
|
|
syncManager.sync().then(() => {
|
|
if(authManager.offline()) {
|
|
// when deleting items while ofline, we need to explictly tell angular to refresh UI
|
|
setTimeout(function () {
|
|
$rootScope.notifyDelete();
|
|
$rootScope.safeApply();
|
|
}, 50);
|
|
} else {
|
|
$timeout(() => {
|
|
$rootScope.notifyDelete();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/*
|
|
Disable dragging and dropping of files into main SN interface.
|
|
both 'dragover' and 'drop' are required to prevent dropping of files.
|
|
This will not prevent extensions from receiving drop events.
|
|
*/
|
|
window.addEventListener('dragover', (event) => {
|
|
event.preventDefault();
|
|
}, false)
|
|
|
|
window.addEventListener('drop', (event) => {
|
|
event.preventDefault();
|
|
alert("Please use FileSafe to attach images and files. Learn more at standardnotes.org/filesafe.")
|
|
}, false)
|
|
|
|
|
|
/*
|
|
Handle Auto Sign In From URL
|
|
*/
|
|
|
|
function urlParam(key) {
|
|
return $location.search()[key];
|
|
}
|
|
|
|
async function autoSignInFromParams() {
|
|
var server = urlParam("server");
|
|
var email = urlParam("email");
|
|
var pw = urlParam("pw");
|
|
|
|
if(!authManager.offline()) {
|
|
// check if current account
|
|
if(await syncManager.getServerURL() === server && authManager.user.email === email) {
|
|
// already signed in, return
|
|
return;
|
|
} else {
|
|
// sign out
|
|
authManager.signout(true).then(() => {
|
|
window.location.reload();
|
|
});
|
|
}
|
|
} else {
|
|
authManager.login(server, email, pw, false, false, {}).then((response) => {
|
|
window.location.reload();
|
|
})
|
|
}
|
|
}
|
|
|
|
if(urlParam("server")) {
|
|
autoSignInFromParams();
|
|
}
|
|
});
|