From f296b0e49ef7f2f04a300a07821327563a27def5 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Fri, 27 Jan 2017 19:39:10 -0600 Subject: [PATCH] functional minimalism --- app/assets/javascripts/app/app.frontend.js | 16 +- .../app/frontend/controllers/editor.js | 4 +- .../app/frontend/controllers/header.js | 10 +- .../app/frontend/controllers/home.js | 6 +- .../app/frontend/controllers/notes.js | 2 +- .../app/frontend/models/app/extension.js | 4 - .../{apiController.js => authManager.js} | 37 +-- .../services/directives/accountDataMenu.js | 27 --- .../directives/accountExportSection.js | 79 ------- .../services/directives/accountKeysSection.js | 28 --- .../directives/accountNewAccountSection.js | 81 ------- .../services/directives/accountSyncSection.js | 65 ------ .../directives/{ => functional}/autofocus.js | 0 .../directives/{ => functional}/delay-hide.js | 0 .../{ => functional}/file-change.js | 0 .../directives/{ => functional}/lowercase.js | 0 .../{ => functional}/selectOnClick.js | 0 .../services/directives/views/accountMenu.js | 152 +++++++++++++ .../{ => views}/contextualExtensionsMenu.js | 0 .../{ => views}/globalExtensionsMenu.js | 0 .../app/services/extensionManager.js | 6 +- .../{sync => helpers}/encryptionHelper.js | 0 .../javascripts/app/services/keyManager.js | 37 --- .../app/services/sync/syncManager.js | 172 -------------- .../app/services/sync/syncProvider.js | 77 ------- .../app/services/sync/syncRunner.js | 214 ------------------ .../javascripts/app/services/syncManager.js | 189 ++++++++++++++++ app/assets/stylesheets/app/_header.scss | 4 - .../directives/account-data-menu.html.haml | 17 -- .../directives/account-menu.html.haml | 61 +++++ .../account-export-section.html.haml | 31 --- .../account-keys-section.html.haml | 18 -- .../account-new-account-section.html.haml | 35 --- .../account-sync-section.html.haml | 30 --- .../templates/frontend/header.html.haml | 2 +- app/assets/templates/services/.keep | 0 36 files changed, 444 insertions(+), 960 deletions(-) rename app/assets/javascripts/app/services/{apiController.js => authManager.js} (91%) delete mode 100644 app/assets/javascripts/app/services/directives/accountDataMenu.js delete mode 100644 app/assets/javascripts/app/services/directives/accountExportSection.js delete mode 100644 app/assets/javascripts/app/services/directives/accountKeysSection.js delete mode 100644 app/assets/javascripts/app/services/directives/accountNewAccountSection.js delete mode 100644 app/assets/javascripts/app/services/directives/accountSyncSection.js rename app/assets/javascripts/app/services/directives/{ => functional}/autofocus.js (100%) rename app/assets/javascripts/app/services/directives/{ => functional}/delay-hide.js (100%) rename app/assets/javascripts/app/services/directives/{ => functional}/file-change.js (100%) rename app/assets/javascripts/app/services/directives/{ => functional}/lowercase.js (100%) rename app/assets/javascripts/app/services/directives/{ => functional}/selectOnClick.js (100%) create mode 100644 app/assets/javascripts/app/services/directives/views/accountMenu.js rename app/assets/javascripts/app/services/directives/{ => views}/contextualExtensionsMenu.js (100%) rename app/assets/javascripts/app/services/directives/{ => views}/globalExtensionsMenu.js (100%) rename app/assets/javascripts/app/services/{sync => helpers}/encryptionHelper.js (100%) delete mode 100644 app/assets/javascripts/app/services/keyManager.js delete mode 100644 app/assets/javascripts/app/services/sync/syncManager.js delete mode 100644 app/assets/javascripts/app/services/sync/syncProvider.js delete mode 100644 app/assets/javascripts/app/services/sync/syncRunner.js create mode 100644 app/assets/javascripts/app/services/syncManager.js delete mode 100644 app/assets/templates/frontend/directives/account-data-menu.html.haml create mode 100644 app/assets/templates/frontend/directives/account-menu.html.haml delete mode 100644 app/assets/templates/frontend/directives/account-menu/account-export-section.html.haml delete mode 100644 app/assets/templates/frontend/directives/account-menu/account-keys-section.html.haml delete mode 100644 app/assets/templates/frontend/directives/account-menu/account-new-account-section.html.haml delete mode 100644 app/assets/templates/frontend/directives/account-menu/account-sync-section.html.haml delete mode 100644 app/assets/templates/services/.keep 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