diff --git a/app/assets/javascripts/app/app.frontend.js b/app/assets/javascripts/app/app.frontend.js
index 60ec42208..da2051625 100644
--- a/app/assets/javascripts/app/app.frontend.js
+++ b/app/assets/javascripts/app/app.frontend.js
@@ -18,6 +18,20 @@ angular.module('app.frontend', [
'restangular'
])
-.config(function (RestangularProvider, apiControllerProvider) {
+.config(function (RestangularProvider, authManagerProvider) {
RestangularProvider.setDefaultHeaders({"Content-Type": "application/json"});
+
+ RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
+ var token = localStorage.getItem("jwt");
+ if(token) {
+ headers = _.extend(headers, {Authorization: "Bearer " + localStorage.getItem("jwt")});
+ }
+
+ return {
+ element: element,
+ params: params,
+ headers: headers,
+ httpConfig: httpConfig
+ };
+ });
})
diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js
index 3ed103bca..43fa67afb 100644
--- a/app/assets/javascripts/app/frontend/controllers/editor.js
+++ b/app/assets/javascripts/app/frontend/controllers/editor.js
@@ -88,7 +88,7 @@ angular.module('app.frontend')
}
}
})
- .controller('EditorCtrl', function ($sce, $timeout, apiController, markdownRenderer, $rootScope, extensionManager, syncManager) {
+ .controller('EditorCtrl', function ($sce, $timeout, authManager, markdownRenderer, $rootScope, extensionManager, syncManager) {
this.setNote = function(note, oldNote) {
this.editorMode = 'edit';
@@ -149,7 +149,7 @@ angular.module('app.frontend')
if(statusTimeout) $timeout.cancel(statusTimeout);
statusTimeout = $timeout(function(){
var status = "All changes saved"
- if(syncManager.offline) {
+ if(authManager.offline()) {
status += " (offline)";
}
this.saveError = false;
diff --git a/app/assets/javascripts/app/frontend/controllers/header.js b/app/assets/javascripts/app/frontend/controllers/header.js
index c6ac21d32..c0e18fd99 100644
--- a/app/assets/javascripts/app/frontend/controllers/header.js
+++ b/app/assets/javascripts/app/frontend/controllers/header.js
@@ -1,5 +1,5 @@
angular.module('app.frontend')
- .directive("header", function(apiController){
+ .directive("header", function(authManager){
return {
restrict: 'E',
scope: {},
@@ -22,17 +22,17 @@ angular.module('app.frontend')
}
}
})
- .controller('HeaderCtrl', function (apiController, modelManager, $timeout, dbManager, syncManager) {
+ .controller('HeaderCtrl', function (authManager, modelManager, $timeout, dbManager, syncManager) {
- this.user = apiController.user;
+ this.user = authManager.user;
this.updateOfflineStatus = function() {
- this.offline = syncManager.offline;
+ this.offline = authManager.offline();
}
this.updateOfflineStatus();
this.findErrors = function() {
- this.error = syncManager.syncProviders.filter(function(provider){return provider.error}).length > 0 ? true : false;
+ this.error = syncManager.error;
}
this.findErrors();
diff --git a/app/assets/javascripts/app/frontend/controllers/home.js b/app/assets/javascripts/app/frontend/controllers/home.js
index 7f46bd22c..aaa854a66 100644
--- a/app/assets/javascripts/app/frontend/controllers/home.js
+++ b/app/assets/javascripts/app/frontend/controllers/home.js
@@ -1,8 +1,8 @@
angular.module('app.frontend')
-.controller('HomeCtrl', function ($scope, $rootScope, $timeout, modelManager, syncManager) {
+.controller('HomeCtrl', function ($scope, $rootScope, $timeout, modelManager, syncManager, authManager) {
$rootScope.bodyClass = "app-body-class";
- syncManager.loadLocalItems(function(items){
+ syncManager.loadLocalItems(function(items) {
$scope.$apply();
syncManager.sync(null);
@@ -138,7 +138,7 @@ angular.module('app.frontend')
}
syncManager.sync(function(){
- if(syncManager.offline) {
+ if(authManager.offline()) {
// when deleting items while ofline, we need to explictly tell angular to refresh UI
setTimeout(function () {
$scope.safeApply();
diff --git a/app/assets/javascripts/app/frontend/controllers/notes.js b/app/assets/javascripts/app/frontend/controllers/notes.js
index 0a9944c2e..a9e4c37f2 100644
--- a/app/assets/javascripts/app/frontend/controllers/notes.js
+++ b/app/assets/javascripts/app/frontend/controllers/notes.js
@@ -24,7 +24,7 @@ angular.module('app.frontend')
}
}
})
- .controller('NotesCtrl', function (apiController, $timeout, $rootScope, modelManager) {
+ .controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager) {
$rootScope.$on("editorFocused", function(){
this.showMenu = false;
diff --git a/app/assets/javascripts/app/frontend/models/app/extension.js b/app/assets/javascripts/app/frontend/models/app/extension.js
index 36f0a8a54..097f9212f 100644
--- a/app/assets/javascripts/app/frontend/models/app/extension.js
+++ b/app/assets/javascripts/app/frontend/models/app/extension.js
@@ -56,10 +56,6 @@ class Extension extends Item {
this.content_type = "Extension";
}
- get syncProviderAction() {
- return _.find(this.actions, {sync_provider: true})
- }
-
actionsInGlobalContext() {
return this.actions.filter(function(action){
return action.context == "global" || action.sync_provider == true;
diff --git a/app/assets/javascripts/app/services/apiController.js b/app/assets/javascripts/app/services/authManager.js
similarity index 91%
rename from app/assets/javascripts/app/services/apiController.js
rename to app/assets/javascripts/app/services/authManager.js
index fc847f9fc..8ccdbca6c 100644
--- a/app/assets/javascripts/app/services/apiController.js
+++ b/app/assets/javascripts/app/services/authManager.js
@@ -1,5 +1,5 @@
angular.module('app.frontend')
- .provider('apiController', function () {
+ .provider('authManager', function () {
function domainName() {
var domain_comps = location.hostname.split(".");
@@ -7,11 +7,11 @@ angular.module('app.frontend')
return domain;
}
- this.$get = function($rootScope, Restangular, modelManager, dbManager, syncManager) {
- return new ApiController($rootScope, Restangular, modelManager, dbManager, syncManager);
+ this.$get = function($rootScope, Restangular, modelManager) {
+ return new AuthManager($rootScope, Restangular, modelManager);
}
- function ApiController($rootScope, Restangular, modelManager, dbManager, syncManager) {
+ function AuthManager($rootScope, Restangular, modelManager) {
var userData = localStorage.getItem("user");
if(userData) {
@@ -24,9 +24,9 @@ angular.module('app.frontend')
}
}
- /*
- Auth
- */
+ this.offline = function() {
+ return !this.user;
+ }
this.getAuthParams = function() {
return JSON.parse(localStorage.getItem("auth_params"));
@@ -91,15 +91,11 @@ angular.module('app.frontend')
}
this.handleAuthResponse = function(response, email, url, authParams, mk) {
- var params = {
- url: url,
- email: email,
- uuid: response.user.uuid,
- ek: mk,
- jwt: response.token,
- auth_params: _.omit(authParams, ["pw_nonce"])
- }
- syncManager.addAccountBasedSyncProvider(params);
+ localStorage.setItem("server", url);
+ localStorage.setItem("user", JSON.stringify(response.plain()));
+ localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"])));
+ localStorage.setItem("mk", mk);
+ localStorage.setItem("jwt", response.token);
}
this.register = function(url, email, password, callback) {
@@ -259,14 +255,5 @@ angular.module('app.frontend')
return JSON.parse(JSON.stringify(object));
}
- this.destroyLocalData = function(callback) {
- dbManager.clearAllItems(function(){
- localStorage.clear();
- if(callback) {
- callback();
- }
- });
- }
-
}
});
diff --git a/app/assets/javascripts/app/services/directives/accountDataMenu.js b/app/assets/javascripts/app/services/directives/accountDataMenu.js
deleted file mode 100644
index 244101b28..000000000
--- a/app/assets/javascripts/app/services/directives/accountDataMenu.js
+++ /dev/null
@@ -1,27 +0,0 @@
-class AccountDataMenu {
-
- constructor() {
- this.restrict = "E";
- this.templateUrl = "frontend/directives/account-data-menu.html";
- this.scope = {};
- }
-
- controller($scope, apiController, modelManager, keyManager) {
- 'ngInject';
-
- $scope.keys = keyManager.keys;
-
- $scope.destroyLocalData = function() {
- if(!confirm("Are you sure you want to end your session? This will delete all local items, sync accounts, keys, and extensions.")) {
- return;
- }
-
- apiController.destroyLocalData(function(){
- window.location.reload();
- })
- }
-
- }
-}
-
-angular.module('app.frontend').directive('accountDataMenu', () => new AccountDataMenu);
diff --git a/app/assets/javascripts/app/services/directives/accountExportSection.js b/app/assets/javascripts/app/services/directives/accountExportSection.js
deleted file mode 100644
index 335ae347b..000000000
--- a/app/assets/javascripts/app/services/directives/accountExportSection.js
+++ /dev/null
@@ -1,79 +0,0 @@
-class AccountExportSection {
-
- constructor() {
- this.restrict = "E";
- this.templateUrl = "frontend/directives/account-menu/account-export-section.html";
- this.scope = {
- };
- }
-
- controller($scope, apiController, $timeout) {
- 'ngInject';
-
- $scope.archiveFormData = {encryption_type: $scope.user ? 'mk' : 'ek'};
- $scope.user = apiController.user;
-
- $scope.downloadDataArchive = function() {
- if($scope.archiveFormData.encryption_type == 'ek') {
- if(!$scope.archiveFormData.ek) {
- alert("You must set an encryption key to export the data encrypted.")
- return;
- }
- }
-
- var link = document.createElement('a');
- link.setAttribute('download', 'notes.json');
-
- var ek = $scope.archiveFormData.encryption_type == 'ek' ? $scope.archiveFormData.ek : null;
- var encrypted = $scope.archiveFormData.encryption_type != 'none';
-
- link.href = apiController.itemsDataFile(encrypted, ek);
- link.click();
- }
-
- $scope.performImport = function(data, password) {
- $scope.importData.loading = true;
- // allow loading indicator to come up with timeout
- $timeout(function(){
- apiController.importJSONData(data, password, function(success, response){
- console.log("Import response:", success, response);
- $scope.importData.loading = false;
- if(success) {
- $scope.importData = null;
- } else {
- alert("There was an error importing your data. Please try again.");
- }
- })
- })
- }
-
- $scope.submitImportPassword = function() {
- $scope.performImport($scope.importData.data, $scope.importData.password);
- }
-
- $scope.importFileSelected = function(files) {
- $scope.importData = {};
-
- var file = files[0];
- var reader = new FileReader();
- reader.onload = function(e) {
- var data = JSON.parse(e.target.result);
- $timeout(function(){
- if(data.auth_params) {
- // request password
- $scope.importData.requestPassword = true;
- $scope.importData.data = data;
- } else {
- $scope.performImport(data, null);
- }
- })
- }
-
- reader.readAsText(file);
- }
-
- }
-
-}
-
-angular.module('app.frontend').directive('accountExportSection', () => new AccountExportSection);
diff --git a/app/assets/javascripts/app/services/directives/accountKeysSection.js b/app/assets/javascripts/app/services/directives/accountKeysSection.js
deleted file mode 100644
index db9526488..000000000
--- a/app/assets/javascripts/app/services/directives/accountKeysSection.js
+++ /dev/null
@@ -1,28 +0,0 @@
-class AccountKeysSection {
-
- constructor() {
- this.restrict = "E";
- this.templateUrl = "frontend/directives/account-menu/account-keys-section.html";
- this.scope = {
- };
- }
-
- controller($scope, apiController, keyManager) {
- 'ngInject';
-
- $scope.newKeyData = {};
- $scope.keys = keyManager.keys;
-
- $scope.submitNewKeyForm = function() {
- var key = keyManager.addKey($scope.newKeyData.name, $scope.newKeyData.key);
- if(!key) {
- alert("This key name is already in use. Please use a different name.");
- return;
- }
-
- $scope.newKeyData.showForm = false;
- }
- }
-}
-
-angular.module('app.frontend').directive('accountKeysSection', () => new AccountKeysSection);
diff --git a/app/assets/javascripts/app/services/directives/accountNewAccountSection.js b/app/assets/javascripts/app/services/directives/accountNewAccountSection.js
deleted file mode 100644
index b7b8161af..000000000
--- a/app/assets/javascripts/app/services/directives/accountNewAccountSection.js
+++ /dev/null
@@ -1,81 +0,0 @@
-class AccountNewAccountSection {
-
- constructor() {
- this.restrict = "E";
- this.templateUrl = "frontend/directives/account-menu/account-new-account-section.html";
- this.scope = {
- };
- }
-
- controller($scope, apiController, modelManager, $timeout, dbManager, syncManager) {
- 'ngInject';
-
- $scope.formData = {url: syncManager.defaultServerURL()};
- $scope.user = apiController.user;
-
- $scope.showForm = syncManager.syncProviders.length == 0;
-
- $scope.changePasswordPressed = function() {
- $scope.showNewPasswordForm = !$scope.showNewPasswordForm;
- }
-
- $scope.submitExternalSyncURL = function() {
- syncManager.addSyncProviderFromURL($scope.formData.secretUrl);
- $scope.formData.showAddLinkForm = false;
- $scope.formData.secretUrl = null;
- $scope.showForm = false;
- }
-
- $scope.submitPasswordChange = function() {
- $scope.passwordChangeData.status = "Generating New Keys...";
-
- $timeout(function(){
- if(data.password != data.password_confirmation) {
- alert("Your new password does not match its confirmation.");
- return;
- }
-
- apiController.changePassword($scope.passwordChangeData.current_password, $scope.passwordChangeData.new_password, function(response){
-
- })
-
- })
- }
-
- $scope.loginSubmitPressed = function() {
- $scope.formData.status = "Generating Login Keys...";
- console.log("logging in with url", $scope.formData.url);
- $timeout(function(){
- apiController.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, function(response){
- $scope.formData.status = null;
- if(!response || response.error) {
- var error = response ? response.error : {message: "An unknown error occured."}
- if(!response || (response && !response.didDisplayAlert)) {
- alert(error.message);
- }
- } else {
- $scope.showForm = false;
- }
- });
- })
- }
-
- $scope.submitRegistrationForm = function() {
- $scope.formData.status = "Generating Account Keys...";
-
- $timeout(function(){
- apiController.register($scope.formData.url, $scope.formData.email, $scope.formData.user_password, function(response){
- $scope.formData.status = null;
- if(!response || response.error) {
- var error = response ? response.error : {message: "An unknown error occured."}
- alert(error.message);
- } else {
- $scope.showForm = false;
- }
- });
- })
- }
- }
-}
-
-angular.module('app.frontend').directive('accountNewAccountSection', () => new AccountNewAccountSection);
diff --git a/app/assets/javascripts/app/services/directives/accountSyncSection.js b/app/assets/javascripts/app/services/directives/accountSyncSection.js
deleted file mode 100644
index 7bb03f4fd..000000000
--- a/app/assets/javascripts/app/services/directives/accountSyncSection.js
+++ /dev/null
@@ -1,65 +0,0 @@
-class AccountSyncSection {
-
- constructor() {
- this.restrict = "E";
- this.templateUrl = "frontend/directives/account-menu/account-sync-section.html";
- this.scope = {
- };
- }
-
- controller($scope, modelManager, keyManager, syncManager) {
- 'ngInject';
-
- $scope.syncManager = syncManager;
- $scope.syncProviders = syncManager.syncProviders;
- $scope.keys = keyManager.keys;
- // $scope.showSection = syncManager.syncProviders.length > 0;
-
- $scope.enableSyncProvider = function(provider, primary) {
- if(!provider.keyName) {
- alert("You must choose an encryption key for this account before enabling it.");
- return;
- }
-
- syncManager.enableSyncProvider(provider, primary);
- }
-
- $scope.removeSyncProvider = function(provider) {
- if(provider.primary) {
- alert("You cannot remove your main sync account. Instead, end your session by destroying all local data. Or, choose another account to be your primary sync account.")
- return;
- }
-
- if(confirm("Are you sure you want to remove this sync account?")) {
- syncManager.removeSyncProvider(provider);
- }
- }
-
- $scope.changeEncryptionKey = function(provider) {
- if(provider.isStandardNotesAccount) {
- alert("To change your encryption key for your Standard File account, you need to change your password. However, this functionality is not currently available.");
- return;
- }
-
- if(!confirm("Changing your encryption key will re-encrypt all your notes with the new key and sync them back to the server. This can take several minutes. We strongly recommend downloading a backup of your notes before continuing.")) {
- return;
- }
-
- provider.formData = {keyName: provider.keyName};
- provider.showKeyForm = true;
- }
-
- $scope.saveKey = function(provider) {
- provider.showKeyForm = false;
- provider.keyName = provider.formData.keyName;
- syncManager.didMakeChangesToSyncProviders();
-
- if(provider.enabled) {
- syncManager.addAllDataAsNeedingSyncForProvider(provider);
- syncManager.sync();
- }
- }
- }
-}
-
-angular.module('app.frontend').directive('accountSyncSection', () => new AccountSyncSection);
diff --git a/app/assets/javascripts/app/services/directives/autofocus.js b/app/assets/javascripts/app/services/directives/functional/autofocus.js
similarity index 100%
rename from app/assets/javascripts/app/services/directives/autofocus.js
rename to app/assets/javascripts/app/services/directives/functional/autofocus.js
diff --git a/app/assets/javascripts/app/services/directives/delay-hide.js b/app/assets/javascripts/app/services/directives/functional/delay-hide.js
similarity index 100%
rename from app/assets/javascripts/app/services/directives/delay-hide.js
rename to app/assets/javascripts/app/services/directives/functional/delay-hide.js
diff --git a/app/assets/javascripts/app/services/directives/file-change.js b/app/assets/javascripts/app/services/directives/functional/file-change.js
similarity index 100%
rename from app/assets/javascripts/app/services/directives/file-change.js
rename to app/assets/javascripts/app/services/directives/functional/file-change.js
diff --git a/app/assets/javascripts/app/services/directives/lowercase.js b/app/assets/javascripts/app/services/directives/functional/lowercase.js
similarity index 100%
rename from app/assets/javascripts/app/services/directives/lowercase.js
rename to app/assets/javascripts/app/services/directives/functional/lowercase.js
diff --git a/app/assets/javascripts/app/services/directives/selectOnClick.js b/app/assets/javascripts/app/services/directives/functional/selectOnClick.js
similarity index 100%
rename from app/assets/javascripts/app/services/directives/selectOnClick.js
rename to app/assets/javascripts/app/services/directives/functional/selectOnClick.js
diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/services/directives/views/accountMenu.js
new file mode 100644
index 000000000..c04148bfd
--- /dev/null
+++ b/app/assets/javascripts/app/services/directives/views/accountMenu.js
@@ -0,0 +1,152 @@
+class AccountMenu {
+
+ constructor() {
+ this.restrict = "E";
+ this.templateUrl = "frontend/directives/account-menu.html";
+ this.scope = {};
+ }
+
+ controller($scope, authManager, modelManager, syncManager, $timeout) {
+ 'ngInject';
+
+ $scope.formData = {url: syncManager.serverURL};
+ $scope.user = authManager.user;
+
+ $scope.changePasswordPressed = function() {
+ $scope.showNewPasswordForm = !$scope.showNewPasswordForm;
+ }
+
+ $scope.submitPasswordChange = function() {
+ $scope.passwordChangeData.status = "Generating New Keys...";
+
+ $timeout(function(){
+ if(data.password != data.password_confirmation) {
+ alert("Your new password does not match its confirmation.");
+ return;
+ }
+
+ authManager.changePassword($scope.passwordChangeData.current_password, $scope.passwordChangeData.new_password, function(response){
+
+ })
+
+ })
+ }
+
+ $scope.loginSubmitPressed = function() {
+ $scope.formData.status = "Generating Login Keys...";
+ console.log("logging in with url", $scope.formData.url);
+ $timeout(function(){
+ authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, function(response){
+ $scope.formData.status = null;
+ if(!response || response.error) {
+ var error = response ? response.error : {message: "An unknown error occured."}
+ if(!response || (response && !response.didDisplayAlert)) {
+ alert(error.message);
+ }
+ } else {
+ window.location.reload();
+ }
+ });
+ })
+ }
+
+ $scope.submitRegistrationForm = function() {
+ $scope.formData.status = "Generating Account Keys...";
+
+ $timeout(function(){
+ authManager.register($scope.formData.url, $scope.formData.email, $scope.formData.user_password, function(response){
+ $scope.formData.status = null;
+ if(!response || response.error) {
+ var error = response ? response.error : {message: "An unknown error occured."}
+ alert(error.message);
+ } else {
+ window.location.reload();
+ }
+ });
+ })
+ }
+
+ $scope.destroyLocalData = function() {
+ if(!confirm("Are you sure you want to end your session? This will delete all local items and extensions.")) {
+ return;
+ }
+
+ syncManager.destroyLocalData(function(){
+ window.location.reload();
+ })
+ }
+
+
+ /* Import/Export */
+
+ $scope.archiveFormData = {encryption_type: $scope.user ? 'mk' : 'ek'};
+ $scope.user = authManager.user;
+
+ $scope.downloadDataArchive = function() {
+ if($scope.archiveFormData.encryption_type == 'ek') {
+ if(!$scope.archiveFormData.ek) {
+ alert("You must set an encryption key to export the data encrypted.")
+ return;
+ }
+ }
+
+ var link = document.createElement('a');
+ link.setAttribute('download', 'notes.json');
+
+ var ek = $scope.archiveFormData.encryption_type == 'ek' ? $scope.archiveFormData.ek : null;
+ var encrypted = $scope.archiveFormData.encryption_type != 'none';
+
+ link.href = authManager.itemsDataFile(encrypted, ek);
+ link.click();
+ }
+
+ $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.importData.loading = false;
+ if(success) {
+ $scope.importData = null;
+ } else {
+ alert("There was an error importing your data. Please try again.");
+ }
+ })
+ })
+ }
+
+ $scope.submitImportPassword = function() {
+ $scope.performImport($scope.importData.data, $scope.importData.password);
+ }
+
+ $scope.importFileSelected = function(files) {
+ $scope.importData = {};
+
+ var file = files[0];
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ var data = JSON.parse(e.target.result);
+ $timeout(function(){
+ if(data.auth_params) {
+ // request password
+ $scope.importData.requestPassword = true;
+ $scope.importData.data = data;
+ } else {
+ $scope.performImport(data, null);
+ }
+ })
+ }
+
+ reader.readAsText(file);
+ }
+
+ $scope.encryptionStatusForNotes = function() {
+ var allNotes = modelManager.filteredNotes;
+ return allNotes.length + "/" + allNotes.length + " notes encrypted";
+ }
+
+ }
+}
+
+angular.module('app.frontend').directive('accountMenu', () => new AccountMenu);
diff --git a/app/assets/javascripts/app/services/directives/contextualExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js
similarity index 100%
rename from app/assets/javascripts/app/services/directives/contextualExtensionsMenu.js
rename to app/assets/javascripts/app/services/directives/views/contextualExtensionsMenu.js
diff --git a/app/assets/javascripts/app/services/directives/globalExtensionsMenu.js b/app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js
similarity index 100%
rename from app/assets/javascripts/app/services/directives/globalExtensionsMenu.js
rename to app/assets/javascripts/app/services/directives/views/globalExtensionsMenu.js
diff --git a/app/assets/javascripts/app/services/extensionManager.js b/app/assets/javascripts/app/services/extensionManager.js
index fd691b79a..9dadd8b34 100644
--- a/app/assets/javascripts/app/services/extensionManager.js
+++ b/app/assets/javascripts/app/services/extensionManager.js
@@ -1,9 +1,9 @@
class ExtensionManager {
- constructor(Restangular, modelManager, apiController, syncManager) {
+ constructor(Restangular, modelManager, authManager, syncManager) {
this.Restangular = Restangular;
this.modelManager = modelManager;
- this.apiController = apiController;
+ this.authManager = authManager;
this.enabledRepeatActionUrls = JSON.parse(localStorage.getItem("enabledRepeatActionUrls")) || [];
this.decryptedExtensions = JSON.parse(localStorage.getItem("decryptedExtensions")) || [];
this.extensionEks = JSON.parse(localStorage.getItem("extensionEks")) || {};
@@ -288,7 +288,7 @@ class ExtensionManager {
performPost(action, extension, params, callback) {
var request = this.Restangular.oneUrl(action.url, action.url);
if(this.extensionUsesEncryptedData(extension)) {
- request.auth_params = this.apiController.getAuthParams();
+ request.auth_params = this.authManager.getAuthParams();
}
_.merge(request, params);
diff --git a/app/assets/javascripts/app/services/sync/encryptionHelper.js b/app/assets/javascripts/app/services/helpers/encryptionHelper.js
similarity index 100%
rename from app/assets/javascripts/app/services/sync/encryptionHelper.js
rename to app/assets/javascripts/app/services/helpers/encryptionHelper.js
diff --git a/app/assets/javascripts/app/services/keyManager.js b/app/assets/javascripts/app/services/keyManager.js
deleted file mode 100644
index 9f108474d..000000000
--- a/app/assets/javascripts/app/services/keyManager.js
+++ /dev/null
@@ -1,37 +0,0 @@
-class KeyManager {
-
- constructor() {
- this.keys = JSON.parse(localStorage.getItem("keys")) || [];
- }
-
- addKey(name, key) {
- var existing = this.keyForName(name);
- if(existing) {
- return null;
- }
-
- var newKey = {name: name, key: key};
- this.keys.push(newKey);
- this.persist();
- return newKey;
- }
-
- keyForName(name) {
- var keyObj = _.find(this.keys, function(key){
- return key.name.toLowerCase() == name.toLowerCase();
- });
-
- return keyObj ? keyObj.key : null;
- }
-
- deleteKey(name) {
- _.pull(this.keys, {name: name});
- this.persist();
- }
-
- persist() {
- localStorage.setItem("keys", JSON.stringify(this.keys));
- }
-}
-
-angular.module('app.frontend').service('keyManager', KeyManager);
diff --git a/app/assets/javascripts/app/services/sync/syncManager.js b/app/assets/javascripts/app/services/sync/syncManager.js
deleted file mode 100644
index 67bc4aacf..000000000
--- a/app/assets/javascripts/app/services/sync/syncManager.js
+++ /dev/null
@@ -1,172 +0,0 @@
-export const SNKeyName = "Standard Notes Key";
-
-class SyncManager {
-
- constructor(modelManager, syncRunner, keyManager) {
- this.modelManager = modelManager;
- this.keyManager = keyManager;
- this.syncRunner = syncRunner;
- this.syncRunner.setOnChangeProviderCallback(function(){
- this.didMakeChangesToSyncProviders();
- }.bind(this))
- this.loadSyncProviders();
- }
-
- get offline() {
- return this.enabledProviders.length == 0;
- }
-
- defaultServerURL() {
- // return "https://n3.standardnotes.org";
- return "http://localhost:3000";
- }
-
- get enabledProviders() {
- return this.syncProviders.filter(function(provider){return provider.enabled == true});
- }
-
- /* Used when adding a new account with */
- markAllOfflineItemsDirtyAndSave() {
-
- }
-
- sync(callback) {
- this.syncRunner.sync(this.enabledProviders, callback);
- }
-
- syncWithProvider(provider, callback) {
- this.syncRunner.performSyncWithProvider(provider, callback);
- }
-
- loadLocalItems(callback) {
- this.syncRunner.loadLocalItems(callback);
- }
-
- syncProviderForURL(url) {
- var provider = _.find(this.syncProviders, {url: url});
- return provider;
- }
-
- findOrCreateSyncProviderForUrl(url) {
- var provider = _.find(this.syncProviders, {url: url});
- if(!provider) {
- provider = new SyncProvider({url: url})
- }
- return provider;
- }
-
- setEncryptionStatusForProviderURL(providerURL, encrypted) {
- this.providerForURL(providerURL).encrypted = encrypted;
- this.didMakeChangesToSyncProviders();
- }
-
- get primarySyncProvider() {
- return _.find(this.syncProviders, {primary: true});
- }
-
- didMakeChangesToSyncProviders() {
- localStorage.setItem("syncProviders", JSON.stringify(_.map(this.syncProviders, function(provider) {
- return provider.asJSON()
- })));
- }
-
- loadSyncProviders() {
- this.syncProviders = [];
- var saved = localStorage.getItem("syncProviders");
- if(saved) {
- var parsed = JSON.parse(saved);
- for(var p of parsed) {
- this.syncProviders.push(new SyncProvider(p));
- }
- } else {
- // no providers saved, this means migrating from old system to new
- // check if user is signed in
- var userJSON = localStorage.getItem("user");
- if(this.offline && userJSON) {
- var user = JSON.parse(userJSON);
- var params = {
- url: localStorage.getItem("server"),
- email: user.email,
- uuid: user.uuid,
- ek: localStorage.getItem("mk"),
- jwt: response.token,
- auth_params: JSON.parse(localStorage.getItem("auth_params")),
- }
- var defaultProvider = this.addAccountBasedSyncProvider(params);
- defaultProvider.syncToken = localStorage.getItem("syncToken");
- localStorage.removeItem("mk");
- localStorage.removeItem("syncToken");
- localStorage.removeItem("auth_params");
- localStorage.removeItem("user");
- localStorage.removeItem("server");
- this.didMakeChangesToSyncProviders();
- }
- }
- }
-
- addAccountBasedSyncProvider({url, email, uuid, ek, jwt, auth_params} = {}) {
- var provider = new SyncProvider({
- url: url + "/items/sync",
- primary: !this.primarySyncProvider,
- email: email,
- uuid: uuid,
- jwt: jwt,
- auth_params: auth_params,
- type: SN.SyncProviderType.account
- });
-
- provider.keyName = provider.name;
-
- this.syncProviders.push(provider);
-
- this.keyManager.addKey(provider.keyName, ek);
-
- this.enableSyncProvider(provider, this.enabledProviders == 0);
-
- return provider;
- }
-
- addSyncProviderFromURL(url) {
- var provider = new SyncProvider({url: url});
- provider.type = SN.SyncProviderType.URL;
- this.syncProviders.push(provider);
- this.didMakeChangesToSyncProviders();
- }
-
- enableSyncProvider(syncProvider, primary) {
- // we want to sync the new provider where our current primary one is
- syncProvider.syncToken = this.primarySyncProvider ? this.primarySyncProvider.syncToken : null;
-
- if(primary) {
- for(var provider of this.syncProviders) {
- provider.primary = false;
- }
- }
-
- syncProvider.enabled = true;
- syncProvider.primary = primary;
-
- // since we're enabling a new provider, we need to send it EVERYTHING we have now.
- this.addAllDataAsNeedingSyncForProvider(syncProvider);
- this.didMakeChangesToSyncProviders();
- this.syncWithProvider(syncProvider);
- }
-
- addAllDataAsNeedingSyncForProvider(syncProvider) {
- syncProvider.addPendingItems(this.modelManager.allItems);
- }
-
- removeSyncProvider(provider) {
- _.pull(this.syncProviders, provider);
- this.didMakeChangesToSyncProviders();
- }
-
- clearSyncToken() {
- var primary = this.primarySyncProvider;
- if(primary) {
- primary.syncToken = null;
- }
- }
-}
-
-angular.module('app.frontend').service('syncManager', SyncManager);
diff --git a/app/assets/javascripts/app/services/sync/syncProvider.js b/app/assets/javascripts/app/services/sync/syncProvider.js
deleted file mode 100644
index 2647931e1..000000000
--- a/app/assets/javascripts/app/services/sync/syncProvider.js
+++ /dev/null
@@ -1,77 +0,0 @@
-SN.SyncProviderType = {
- Account: 1,
- URL: 2
-}
-
-class SyncProvider {
-
- constructor(obj) {
- this.encrypted = true;
- this.syncStatus = new SyncStatus();
- _.merge(this, obj);
- }
-
- addPendingItems(items) {
- if(!this.pendingItems) {
- this.pendingItems = [];
- }
-
- this.pendingItems = _.uniqBy(this.pendingItems.concat(items), "uuid");
- }
-
- removePendingItems(items) {
- this.pendingItems = _.difference(this.pendingItems, items);
- }
-
- get isStandardNotesAccount() {
- return this.keyName == SNKeyName;
- }
-
- get secondary() {
- return this.status == "secondary";
- }
-
- get status() {
- if(!this.enabled) {
- return null;
- }
-
- if(this.primary) return "primary";
- else return "secondary";
- }
-
- get name() {
- if(this.type == SN.SyncProviderType.account) {
- return this.email + "@" + this.url;
- } else {
- return this.url;
- }
- }
-
- asJSON() {
- return {
- enabled: this.enabled,
- url: this.url,
- type: this.type,
- primary: this.primary,
- keyName: this.keyName,
- syncToken: this.syncToken,
-
- // account based
- email: this.email,
- uuid: this.uuid,
- jwt: this.jwt,
- auth_params: this.auth_params
- }
- }
-}
-
-class SyncStatus {
- constructor() {
-
- }
-
- get statusString() {
- return `${this.current}/${this.total}`
- }
-}
diff --git a/app/assets/javascripts/app/services/sync/syncRunner.js b/app/assets/javascripts/app/services/sync/syncRunner.js
deleted file mode 100644
index 89c123182..000000000
--- a/app/assets/javascripts/app/services/sync/syncRunner.js
+++ /dev/null
@@ -1,214 +0,0 @@
-class SyncRunner {
-
- constructor($rootScope, modelManager, dbManager, keyManager, Restangular) {
- this.rootScope = $rootScope;
- this.modelManager = modelManager;
- this.dbManager = dbManager;
- this.keyManager = keyManager;
- this.Restangular = Restangular;
- }
-
- setOnChangeProviderCallback(callback) {
- this.onChangeProviderCallback = callback;
- }
-
- didMakeChangesToSyncProvider(provider) {
- this.onChangeProviderCallback(provider);
- }
-
- writeItemsToLocalStorage(items, offlineOnly, callback) {
- var params = items.map(function(item) {
- var itemParams = new ItemParams(item, null);
- itemParams = itemParams.paramsForLocalStorage();
- if(offlineOnly) {
- delete itemParams.dirty;
- }
- return itemParams;
- }.bind(this));
-
- this.dbManager.saveItems(params, callback);
- }
-
- loadLocalItems(callback) {
- var params = this.dbManager.getAllItems(function(items){
- var items = this.handleItemsResponse(items, null, null);
- Item.sortItemsByDate(items);
- callback(items);
- }.bind(this))
- }
-
- syncOffline(items, callback) {
- this.writeItemsToLocalStorage(items, true, function(responseItems){
- // delete anything needing to be deleted
- for(var item of items) {
- if(item.deleted) {
- this.modelManager.removeItemLocally(item);
- }
- }
- }.bind(this))
-
- if(callback) {
- callback();
- }
- }
-
- sync(providers, callback, options = {}) {
-
- var allDirtyItems = this.modelManager.getDirtyItems();
-
- // we want to write all dirty items to disk only if the user has no sync providers, 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(providers.length == 0) {
- this.syncOffline(allDirtyItems, callback);
- }
-
- for(let provider of providers) {
- provider.addPendingItems(allDirtyItems);
- this.didMakeChangesToSyncProvider(provider);
-
- this.__performSyncWithProvider(provider, options, function(response){
- if(provider.primary) {
- if(callback) {
- callback(response)
- }
- }
- })
- }
-
- this.modelManager.clearDirtyItems(allDirtyItems);
- }
-
- performSyncWithProvider(provider, callback) {
- this.__performSyncWithProvider(provider, {}, callback);
- }
-
- __performSyncWithProvider(provider, options, callback) {
- if(provider.syncOpInProgress) {
- provider.repeatOnCompletion = true;
- console.log("Sync op in progress for provider; returning.", provider);
- return;
- }
-
- var isContinuationSync = provider.needsMoreSync;
-
- provider.repeatOnCompletion = false;
- provider.syncOpInProgress = true;
-
- let submitLimit = 100;
- var allItems = provider.pendingItems;
- var subItems = allItems.slice(0, submitLimit);
- if(subItems.length < allItems.length) {
- // more items left to be synced, repeat
- provider.needsMoreSync = true;
- } else {
- provider.needsMoreSync = false;
- }
-
- if(!isContinuationSync) {
- provider.syncStatus.total = allItems.length;
- provider.syncStatus.current = 0;
- }
-
- // Remove dirty items now. If this operation fails, we'll re-add them.
- // This allows us to queue changes on the same item
- provider.removePendingItems(subItems);
-
- var request = this.Restangular.oneUrl(provider.url, provider.url);
- request.limit = 150;
- request.items = _.map(subItems, function(item){
- var key = this.keyManager.keyForName(provider.keyName);
- var itemParams = new ItemParams(item, key);
- itemParams.additionalFields = options.additionalFields;
- return itemParams.paramsForSync();
- }.bind(this));
-
- request.sync_token = provider.syncToken;
- request.cursor_token = provider.cursorToken;
- console.log("Syncing with provider:", provider, "items:", subItems.length, "token", request.sync_token);
-
- var headers = provider.jwt ? {Authorization: "Bearer " + provider.jwt} : {};
- request.post("", undefined, undefined, headers).then(function(response) {
- provider.error = null;
-
- console.log("Completed sync for provider:", provider.url, "Response:", response.plain());
-
- provider.syncToken = response.sync_token;
-
- if(provider.primary) {
- this.rootScope.$broadcast("sync:updated_token", provider.syncToken);
-
- // handle cursor token (more results waiting, perform another sync)
- provider.cursorToken = response.cursor_token;
-
- var retrieved = this.handleItemsResponse(response.retrieved_items, null, provider);
- // merge only metadata for saved items
- var omitFields = ["content", "auth_hash"];
- var saved = this.handleItemsResponse(response.saved_items, omitFields, provider);
-
- this.handleUnsavedItemsResponse(response.unsaved, provider)
-
- this.writeItemsToLocalStorage(saved, false, null);
- this.writeItemsToLocalStorage(retrieved, false, null);
- }
-
- provider.syncOpInProgress = false;
- provider.syncStatus.current += subItems.length;
-
- if(provider.cursorToken || provider.repeatOnCompletion || provider.needsMoreSync) {
- this.__performSyncWithProvider(provider, options, callback);
- } else {
- if(callback) {
- callback(response);
- }
- }
-
- }.bind(this))
- .catch(function(response){
- console.log("Sync error: ", response);
- var error = response.data ? response.data.error : {message: "Could not connect to server."};
-
- // Re-add subItems since this operation failed. We'll have to try again.
- provider.addPendingItems(subItems);
- provider.syncOpInProgress = false;
- provider.error = error;
-
- if(provider.primary) {
- this.writeItemsToLocalStorage(allItems, false, null);
- }
-
- this.rootScope.$broadcast("sync:error", error);
-
- if(callback) {
- callback({error: "Sync error"});
- }
- }.bind(this))
- }
-
- handleUnsavedItemsResponse(unsaved, provider) {
- if(unsaved.length == 0) {
- return;
- }
-
- console.log("Handle unsaved", unsaved);
- for(var mapping of unsaved) {
- var itemResponse = mapping.item;
- var item = this.modelManager.findItem(itemResponse.uuid);
- var error = mapping.error;
- if(error.tag == "uuid_conflict") {
- item.alternateUUID();
- item.setDirty(true);
- item.markAllReferencesDirty();
- }
- }
-
- this.__performSyncWithProvider(provider, {additionalFields: ["created_at", "updated_at"]}, null);
- }
-
- handleItemsResponse(responseItems, omitFields, syncProvider) {
- var ek = syncProvider ? this.keyManager.keyForName(syncProvider.keyName) : null;
- EncryptionHelper.decryptMultipleItems(responseItems, ek);
- return this.modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields);
- }
-}
-
-angular.module('app.frontend').service('syncRunner', SyncRunner);
diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js
new file mode 100644
index 000000000..90239e622
--- /dev/null
+++ b/app/assets/javascripts/app/services/syncManager.js
@@ -0,0 +1,189 @@
+class SyncManager {
+
+ constructor($rootScope, modelManager, authManager, dbManager, Restangular) {
+ this.$rootScope = $rootScope;
+ this.modelManager = modelManager;
+ this.authManager = authManager;
+ this.Restangular = Restangular;
+ this.dbManager = dbManager;
+ this.syncStatus = {};
+ }
+
+ get serverURL() {
+ return localStorage.getItem("server") || "http://localhost:3000";
+ }
+
+ writeItemsToLocalStorage(items, offlineOnly, callback) {
+ var params = items.map(function(item) {
+ var itemParams = new ItemParams(item, null);
+ itemParams = itemParams.paramsForLocalStorage();
+ if(offlineOnly) {
+ delete itemParams.dirty;
+ }
+ return itemParams;
+ }.bind(this));
+
+ this.dbManager.saveItems(params, callback);
+ }
+
+ loadLocalItems(callback) {
+ var params = this.dbManager.getAllItems(function(items){
+ var items = this.handleItemsResponse(items, null, null);
+ Item.sortItemsByDate(items);
+ callback(items);
+ }.bind(this))
+ }
+
+ syncOffline(items, callback) {
+ this.writeItemsToLocalStorage(items, true, function(responseItems){
+ // delete anything needing to be deleted
+ for(var item of items) {
+ if(item.deleted) {
+ this.modelManager.removeItemLocally(item);
+ }
+ }
+ }.bind(this))
+
+ if(callback) {
+ callback();
+ }
+ }
+
+ get syncURL() {
+ return this.serverURL + "/items/sync";
+ }
+
+ sync(callback, options = {}) {
+
+ if(this.syncOpInProgress) {
+ this.repeatOnCompletion = true;
+ console.log("Sync op in progress; returning.");
+ return;
+ }
+
+ var allDirtyItems = this.modelManager.getDirtyItems();
+
+ // 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()) {
+ this.syncOffline(allDirtyItems, callback);
+ this.modelManager.clearDirtyItems(allDirtyItems);
+ return;
+ }
+
+ var isContinuationSync = this.needsMoreSync;
+
+ this.repeatOnCompletion = false;
+ this.syncOpInProgress = true;
+
+ let submitLimit = 100;
+ var subItems = allDirtyItems.slice(0, submitLimit);
+ if(subItems.length < allDirtyItems.length) {
+ // more items left to be synced, repeat
+ this.needsMoreSync = true;
+ } else {
+ this.needsMoreSync = false;
+ }
+
+ if(!isContinuationSync) {
+ this.syncStatus.total = allDirtyItems.length;
+ this.syncStatus.current = 0;
+ }
+
+ var request = this.Restangular.oneUrl(this.syncURL, this.syncURL);
+ request.limit = 150;
+ request.items = _.map(subItems, function(item){
+ var itemParams = new ItemParams(item, localStorage.getItem("mk"));
+ itemParams.additionalFields = options.additionalFields;
+ return itemParams.paramsForSync();
+ }.bind(this));
+
+ request.sync_token = this.syncToken;
+ request.cursor_token = this.cursorToken;
+
+ request.post().then(function(response) {
+ this.modelManager.clearDirtyItems(subItems);
+ this.error = null;
+ this.syncToken = response.sync_token;
+ this.cursorToken = response.cursor_token;
+
+ this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
+
+ var retrieved = this.handleItemsResponse(response.retrieved_items, null);
+ // merge only metadata for saved items
+ var omitFields = ["content", "auth_hash"];
+ var saved = this.handleItemsResponse(response.saved_items, omitFields);
+
+ this.handleUnsavedItemsResponse(response.unsaved)
+
+ this.writeItemsToLocalStorage(saved, false, null);
+ this.writeItemsToLocalStorage(retrieved, false, null);
+
+ this.syncOpInProgress = false;
+ this.syncStatus.current += subItems.length;
+
+ if(this.cursorToken || this.repeatOnCompletion || this.needsMoreSync) {
+ this.sync(callback, options);
+ } else {
+ if(callback) {
+ callback(response);
+ }
+ }
+
+ }.bind(this))
+ .catch(function(response){
+ console.log("Sync error: ", response);
+ var error = response.data ? response.data.error : {message: "Could not connect to server."};
+
+ this.syncOpInProgress = false;
+ this.error = error;
+ this.writeItemsToLocalStorage(allDirtyItems, false, null);
+
+ this.$rootScope.$broadcast("sync:error", error);
+
+ if(callback) {
+ callback({error: "Sync error"});
+ }
+ }.bind(this))
+ }
+
+ handleUnsavedItemsResponse(unsaved) {
+ if(unsaved.length == 0) {
+ return;
+ }
+
+ console.log("Handle unsaved", unsaved);
+ for(var mapping of unsaved) {
+ var itemResponse = mapping.item;
+ var item = this.modelManager.findItem(itemResponse.uuid);
+ var error = mapping.error;
+ if(error.tag == "uuid_conflict") {
+ item.alternateUUID();
+ item.setDirty(true);
+ item.markAllReferencesDirty();
+ }
+ }
+
+ this.sync(null, {additionalFields: ["created_at", "updated_at"]});
+ }
+
+ handleItemsResponse(responseItems, omitFields) {
+ EncryptionHelper.decryptMultipleItems(responseItems, localStorage.getItem("mk"));
+ return this.modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields);
+ }
+
+ clearSyncToken() {
+ localStorage.removeItem("syncToken");
+ }
+
+ destroyLocalData(callback) {
+ this.dbManager.clearAllItems(function(){
+ localStorage.clear();
+ if(callback) {
+ callback();
+ }
+ });
+ }
+}
+
+angular.module('app.frontend').service('syncManager', SyncManager);
diff --git a/app/assets/stylesheets/app/_header.scss b/app/assets/stylesheets/app/_header.scss
index 54663a566..c0c912a7c 100644
--- a/app/assets/stylesheets/app/_header.scss
+++ b/app/assets/stylesheets/app/_header.scss
@@ -239,10 +239,6 @@
cursor: default !important;
}
-.account-panel {
- width: 350px;
-}
-
.import-password {
margin-top: 14px;
diff --git a/app/assets/templates/frontend/directives/account-data-menu.html.haml b/app/assets/templates/frontend/directives/account-data-menu.html.haml
deleted file mode 100644
index 2b5128125..000000000
--- a/app/assets/templates/frontend/directives/account-data-menu.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-.panel.panel-default.account-panel.panel-right.account-data-menu
- .panel-body
-
- %section.gray-bg.medium-padding{"ng-init" => "showSN = true"}
- %account-new-account-section
-
- %section.gray-bg.medium-padding
- %account-sync-section
-
- %section.gray-bg.medium-padding
- %account-export-section
-
- %section.gray-bg.medium-padding
- %account-keys-section
-
- %h4
- %a{"ng-click" => "destroyLocalData()"} Destroy all local data
diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml
new file mode 100644
index 000000000..167b4117e
--- /dev/null
+++ b/app/assets/templates/frontend/directives/account-menu.html.haml
@@ -0,0 +1,61 @@
+.panel.panel-default.panel-right.account-data-menu
+ .panel-body
+
+ -# If not user
+ %div{"ng-if" => "!user"}
+ %p Enter your Standard File account information. You can also register for free using the default server address.
+ .small-v-space
+
+ %form.account-form.mt-5{'name' => "loginForm"}
+ %input.form-control{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'}
+ %input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'formData.email'}
+ %input.form-control{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'}
+
+ %div{"ng-if" => "!formData.status"}
+ %button.btn.dark-button.half-button{"ng-click" => "loginSubmitPressed()", "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
+ %span Sign In
+ %button.btn.dark-button.half-button{"ng-click" => "submitRegistrationForm()", "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
+ %span Register
+ %br
+ .block{"style" => "margin-top: 10px; font-size: 14px; font-weight: bold; text-align: center;"}
+ %a.btn{"ng-click" => "showResetForm = !showResetForm"} Passwords cannot be forgotten.
+
+ %em.block.center-align.mt-10{"ng-if" => "formData.status", "style" => "font-size: 14px;"} {{formData.status}}
+
+ %div{"ng-if" => "showResetForm"}
+ %p{"style" => "font-size: 13px; text-align: center;"}
+ Because notes are locally encrypted using a secret key derived from your password, there's no way to decrypt these notes if you forget your password.
+ For this reason, Standard Notes cannot offer a password reset option. You must make sure to store or remember your password.
+ -# End if not user
+
+ -# If user
+ %div{"ng-if" => "user"}
+ %label Local Encryption
+ %p Notes are encrypted locally before being sent to the server. Neither the server owner nor an intrusive entity can decrypt your locally encrypted notes.
+ %label Status:
+ {{encryptionStatusForNotes()}}
+ -# End if user
+
+ .mt-5{"ng-if" => "user"}
+ %label{"ng-if" => "user"}
+ %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"}
+ Encrypted
+ %label
+ %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"}
+ Decrypted
+ %a{"ng-click" => "downloadDataArchive()"} Download Data Archive
+
+ %div{"ng-if" => "!importData.loading"}
+ %label#import-archive
+ %input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"}
+ %a.disabled
+ %span
+ Import Data from Archive
+ %div{"ng-if" => "importData.requestPassword"}
+ Enter the account password associated with the import file.
+ %input{"type" => "text", "ng-model" => "importData.password"}
+ %button{"ng-click" => "submitImportPassword()"} Decrypt & Import
+
+ .spinner{"ng-if" => "importData.loading"}
+
+ %a{"ng-click" => "destroyLocalData()"} Destroy all local data
diff --git a/app/assets/templates/frontend/directives/account-menu/account-export-section.html.haml b/app/assets/templates/frontend/directives/account-menu/account-export-section.html.haml
deleted file mode 100644
index b22b6d178..000000000
--- a/app/assets/templates/frontend/directives/account-menu/account-export-section.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-%h3{"ng-click" => "showSection = !showSection"}
- %a Import or export data
-
-%div{"ng-if" => "showSection"}
- .options{"style" => "font-size: 12px; margin-top: 4px;"}
- %label{"ng-if" => "user"}
- %input{"type" => "radio", "ng-model" => "archiveFormData.encryption_type", "ng-value" => "'mk'", "ng-change" => "archiveFormData.encryption_type = 'mk'"}
- Encrypted with Standard File key
- %label
- %input{"type" => "radio", "ng-model" => "archiveFormData.encryption_type", "ng-value" => "'ek'", "ng-change" => "archiveFormData.encryption_type = 'ek'"}
- {{user ? 'Encrypted with custom key' : 'Encrypted' }}
- %div{"ng-if" => "!user || (user && archiveFormData.encryption_type == 'ek')"}
- %input{"ng-model" => "archiveFormData.ek", "placeholder" => "Encryption key"}
- %label
- %input{"type" => "radio", "ng-model" => "archiveFormData.encryption_type", "ng-value" => "'none'", "ng-change" => "archiveFormData.encryption_type = 'none'"}
- Decrypted
-
- %a{"ng-click" => "downloadDataArchive()"} Download Data Archive
-
- %div{"ng-if" => "!importData.loading"}
- %label#import-archive
- %input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"}
- %a.disabled
- %span
- Import Data from Archive
- %div{"ng-if" => "importData.requestPassword"}
- Enter the account password associated with the import file.
- %input{"type" => "text", "ng-model" => "importData.password"}
- %button{"ng-click" => "submitImportPassword()"} Decrypt & Import
-
- .spinner{"ng-if" => "importData.loading"}
diff --git a/app/assets/templates/frontend/directives/account-menu/account-keys-section.html.haml b/app/assets/templates/frontend/directives/account-menu/account-keys-section.html.haml
deleted file mode 100644
index 7295cb611..000000000
--- a/app/assets/templates/frontend/directives/account-menu/account-keys-section.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-%h3{"ng-click" => "showSection = !showSection"}
- %a Manage keys
-
-%div{"ng-if" => "showSection"}
- %h4 Encryption Keys
-
- %div{"ng-if" => "showSection"}
- %p Keys are used to encrypt and decrypt your data.
- .mt-10
- %section.white-bg{"ng-repeat" => "key in keys track by key.name"}
- %label {{key.name}}
- %p.wrap {{key.key}}
-
- %a.block.mt-10{"ng-click" => "newKeyData.showForm = !newKeyData.showForm"} Add New Key
- %form{"ng-if" => "newKeyData.showForm"}
- %input{"ng-model" => "newKeyData.name", "placeholder" => "Name your key"}
- %input{"ng-model" => "newKeyData.key", "placeholder" => "Key"}
- %button.light{"ng-click" => "submitNewKeyForm()"} Add Key
diff --git a/app/assets/templates/frontend/directives/account-menu/account-new-account-section.html.haml b/app/assets/templates/frontend/directives/account-menu/account-new-account-section.html.haml
deleted file mode 100644
index 6a9b3cbfa..000000000
--- a/app/assets/templates/frontend/directives/account-menu/account-new-account-section.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-%h3{"ng-click" => "showForm = !showForm"}
- %a Add a sync account
-%div{"ng-if" => "showForm"}
- %p Enter your Standard File account information.
- .small-v-space
-
- %form.account-form{'name' => "loginForm"}
- %input.form-control{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'}
- %input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'formData.email'}
- %input.form-control{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'}
-
- %div{"ng-if" => "!formData.status"}
- %button.btn.dark-button.half-button{"ng-click" => "loginSubmitPressed()", "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
- %span Sign In
- %button.btn.dark-button.half-button{"ng-click" => "submitRegistrationForm()", "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
- %span Register
- %br
- .block{"style" => "margin-top: 10px; font-size: 14px; font-weight: bold; text-align: center;"}
- %a.btn{"ng-click" => "showResetForm = !showResetForm"} Passwords cannot be forgotten.
-
- %div{"ng-if" => "!formData.status"}
- %label.center-align.block.faded — OR —
- %a.block.center-align.medium-text{"ng-if" => "!formData.showAddLinkForm", "ng-click" => "formData.showAddLinkForm = true"} Add sync using secret link
- %form{"ng-if" => "formData.showAddLinkForm"}
- %input.form-control{:autofocus => 'autofocus', :name => 'url', :placeholder => 'Secret URL', :required => true, :type => 'url', 'ng-model' => 'formData.secretUrl'}
- %button.btn.dark-button.btn-block{"ng-click" => "submitExternalSyncURL()"}
- Add Sync Account
- %a.block.center-align.mt-5{"ng-click" => "formData.showAddLinkForm = false"} Cancel
-
- %em.block.center-align.mt-10{"ng-if" => "formData.status", "style" => "font-size: 14px;"} {{formData.status}}
-
- %div{"ng-if" => "showResetForm"}
- %p{"style" => "font-size: 13px; text-align: center;"}
- Because notes are locally encrypted using a secret key derived from your password, there's no way to decrypt these notes if you forget your password.
- For this reason, Standard Notes cannot offer a password reset option. You must make sure to store or remember your password.
diff --git a/app/assets/templates/frontend/directives/account-menu/account-sync-section.html.haml b/app/assets/templates/frontend/directives/account-menu/account-sync-section.html.haml
deleted file mode 100644
index 194e7e8eb..000000000
--- a/app/assets/templates/frontend/directives/account-menu/account-sync-section.html.haml
+++ /dev/null
@@ -1,30 +0,0 @@
-%h3{"ng-click" => "showSection = !showSection"}
- %a Your sync accounts ({{syncProviders.length}})
-
-%div{"ng-if" => "showSection || syncManager.syncProviders.length > 0"}
- .small-v-space
- %section.white-bg.medium-padding{"ng-repeat" => "provider in syncProviders"}
- %label {{!provider.enabled ? 'Not enabled' : (provider.primary ? 'Main' : 'Secondary')}}
- %em{"ng-if" => "provider.keyName"} Using key: {{provider.keyName}}
- %p {{provider.url}}
- %section.inline-h
- %div{"ng-if" => "!provider.keyName || provider.showKeyForm"}
- %p
- %strong Choose encryption key:
- %select{"ng-model" => "provider.formData.keyName"}
- %option{"ng-repeat" => "key in keys", "ng-selected" => "{{key.name == provider.formData.keyName}}", "value" => "{{key.name}}"}
- {{key.name}}
- %button{"ng-click" => "saveKey(provider)"} Set
-
- %button.light{"ng-if" => "!provider.enabled || !provider.primary", "ng-click" => "enableSyncProvider(provider, true)"} Set as Main
- %button.light{"ng-if" => "syncProviders.length > 1 && !provider.secondary && (!provider.primary || !provider.enabled)", "ng-click" => "enableSyncProvider(provider, false)"} Add as Secondary
-
- %button.light{"ng-if" => "provider.keyName", "ng-click" => "changeEncryptionKey(provider)"} Change Encryption Key
- %button.light{"ng-click" => "removeSyncProvider(provider)"} Remove Account
-
- .mt-15{"ng-if" => "provider.error"}
- %strong.red Error syncing: {{provider.error.message}}
- .mt-15{"style" => "height: 15px;", "delay-hide" => "true", "show" => "provider.syncOpInProgress", "delay" => "1000"}
- .spinner{"style" => "float: left; margin-top: 3px; margin-left: 2px;"}
- %strong{"style" => "float: left; margin-left: 7px;"} Syncing:
- {{provider.syncStatus.statusString}}
diff --git a/app/assets/templates/frontend/header.html.haml b/app/assets/templates/frontend/header.html.haml
index f1a72f35f..f385f293c 100644
--- a/app/assets/templates/frontend/header.html.haml
+++ b/app/assets/templates/frontend/header.html.haml
@@ -2,7 +2,7 @@
.pull-left
.footer-bar-link
%a{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account
- %account-data-menu{"ng-if" => "ctrl.showAccountMenu"}
+ %account-menu{"ng-if" => "ctrl.showAccountMenu"}
.footer-bar-link
%a{"ng-click" => "ctrl.toggleExtensions()"} Extensions
diff --git a/app/assets/templates/services/.keep b/app/assets/templates/services/.keep
deleted file mode 100644
index e69de29bb..000000000