diff --git a/app/assets/javascripts/app/frontend/controllers/header.js b/app/assets/javascripts/app/frontend/controllers/header.js index c0e18fd99..624d3b391 100644 --- a/app/assets/javascripts/app/frontend/controllers/header.js +++ b/app/assets/javascripts/app/frontend/controllers/header.js @@ -32,11 +32,10 @@ angular.module('app.frontend') this.updateOfflineStatus(); this.findErrors = function() { - this.error = syncManager.error; + this.error = syncManager.syncStatus.error; } this.findErrors(); - this.accountMenuPressed = function() { this.serverData = {}; this.showAccountMenu = !this.showAccountMenu; diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 8ccdbca6c..d2b0ea24c 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -92,7 +92,7 @@ angular.module('app.frontend') this.handleAuthResponse = function(response, email, url, authParams, mk) { localStorage.setItem("server", url); - localStorage.setItem("user", JSON.stringify(response.plain())); + localStorage.setItem("user", JSON.stringify(response.plain().user)); localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"]))); localStorage.setItem("mk", mk); localStorage.setItem("jwt", response.token); @@ -243,10 +243,8 @@ angular.module('app.frontend') items: items } - if(ek.name == SNKeyName) { - // auth params are only needed when encrypted with a standard file key - data["auth_params"] = this.getAuthParams(); - } + // auth params are only needed when encrypted with a standard file key + data["auth_params"] = this.getAuthParams(); return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */)); } diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/services/directives/views/accountMenu.js index c04148bfd..a29a90db8 100644 --- a/app/assets/javascripts/app/services/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/services/directives/views/accountMenu.js @@ -11,6 +11,9 @@ class AccountMenu { $scope.formData = {url: syncManager.serverURL}; $scope.user = authManager.user; + $scope.server = syncManager.serverURL; + + $scope.syncStatus = syncManager.syncStatus; $scope.changePasswordPressed = function() { $scope.showNewPasswordForm = !$scope.showNewPasswordForm; @@ -37,14 +40,14 @@ class AccountMenu { 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) { + $scope.formData.status = null; var error = response ? response.error : {message: "An unknown error occured."} if(!response || (response && !response.didDisplayAlert)) { alert(error.message); } } else { - window.location.reload(); + $scope.onAuthSuccess(); } }); }) @@ -55,17 +58,23 @@ class AccountMenu { $timeout(function(){ authManager.register($scope.formData.url, $scope.formData.email, $scope.formData.user_password, function(response){ - $scope.formData.status = null; if(!response || response.error) { + $scope.formData.status = null; var error = response ? response.error : {message: "An unknown error occured."} alert(error.message); } else { - window.location.reload(); + $scope.onAuthSuccess(); } }); }) } + $scope.onAuthSuccess = function() { + syncManager.markAllItemsDirtyAndSaveOffline(function(){ + 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; @@ -79,24 +88,16 @@ class AccountMenu { /* Import/Export */ - $scope.archiveFormData = {encryption_type: $scope.user ? 'mk' : 'ek'}; + $scope.archiveFormData = {encrypted: $scope.user ? true : false}; $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'; + var ek = $scope.archiveFormData.encrypted ? syncManager.masterKey : null; - link.href = authManager.itemsDataFile(encrypted, ek); + link.href = authManager.itemsDataFile(ek); link.click(); } diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index 90239e622..e2c8aa1ef 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -13,6 +13,10 @@ class SyncManager { return localStorage.getItem("server") || "http://localhost:3000"; } + get masterKey() { + return localStorage.getItem("mk"); + } + writeItemsToLocalStorage(items, offlineOnly, callback) { var params = items.map(function(item) { var itemParams = new ItemParams(item, null); @@ -49,13 +53,21 @@ class SyncManager { } } + markAllItemsDirtyAndSaveOffline(callback) { + var items = this.modelManager.allItems; + for(var item of items) { + item.setDirty(true); + } + this.writeItemsToLocalStorage(items, false, callback); + } + get syncURL() { return this.serverURL + "/items/sync"; } sync(callback, options = {}) { - if(this.syncOpInProgress) { + if(this.syncStatus.syncOpInProgress) { this.repeatOnCompletion = true; console.log("Sync op in progress; returning."); return; @@ -74,7 +86,7 @@ class SyncManager { var isContinuationSync = this.needsMoreSync; this.repeatOnCompletion = false; - this.syncOpInProgress = true; + this.syncStatus.syncOpInProgress = true; let submitLimit = 100; var subItems = allDirtyItems.slice(0, submitLimit); @@ -103,7 +115,7 @@ class SyncManager { request.post().then(function(response) { this.modelManager.clearDirtyItems(subItems); - this.error = null; + this.syncStatus.error = null; this.syncToken = response.sync_token; this.cursorToken = response.cursor_token; @@ -119,7 +131,7 @@ class SyncManager { this.writeItemsToLocalStorage(saved, false, null); this.writeItemsToLocalStorage(retrieved, false, null); - this.syncOpInProgress = false; + this.syncStatus.syncOpInProgress = false; this.syncStatus.current += subItems.length; if(this.cursorToken || this.repeatOnCompletion || this.needsMoreSync) { @@ -135,8 +147,8 @@ class SyncManager { 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.syncStatus.syncOpInProgress = false; + this.syncStatus.error = error; this.writeItemsToLocalStorage(allDirtyItems, false, null); this.$rootScope.$broadcast("sync:error", error); diff --git a/app/assets/stylesheets/app/_header.scss b/app/assets/stylesheets/app/_header.scss index c0c912a7c..9f4bb069a 100644 --- a/app/assets/stylesheets/app/_header.scss +++ b/app/assets/stylesheets/app/_header.scss @@ -18,6 +18,10 @@ margin-top: 15px !important; } +.mt-25 { + margin-top: 25px !important; +} + .mb-10 { margin-bottom: 10px !important; } @@ -54,22 +58,54 @@ display: block; } +.medium-v-space { + height: 12px; + display: block; +} + +.large-v-space { + height: 24px; + display: block; +} + .medium-padding { padding: 10px !important; } .large-padding { - padding: 15px !important; + padding: 22px !important; } .red { color: red !important; } +.blue { + color: $blue-color; +} + .bold { font-weight: bold !important; } +.normal { + font-weight: normal !important; +} + +.inline { + display: inline-block; +} + +.fake-link { + font-weight: bold; + cursor: pointer; + color: $blue-color; + + &:hover { + text-decoration: underline; + } +} + .footer-bar { position: relative; width: 100%; @@ -114,6 +150,11 @@ display: block; } + h2 { + margin-bottom: 0px; + margin-top: 0px; + } + h3 { font-size: 14px !important; margin-top: 4px !important; @@ -122,6 +163,7 @@ h4 { margin-bottom: 0px !important; + font-size: 13px !important; } section { diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 167b4117e..18df4f476 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -1,7 +1,5 @@ .panel.panel-default.panel-right.account-data-menu - .panel-body - - -# If not user + .panel-body.large-padding %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 @@ -26,31 +24,38 @@ %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 + %h2 {{user.email}} + %p {{server}} + + %p.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress", "delay" => "1000"} Syncing: {{syncStatus.current}}/{{syncStatus.total}} + %p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}} + + .medium-v-space + + %h4 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 + %div.mt-5 + %label Status: + {{encryptionStatusForNotes()}} - .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 + .mt-25{"ng-if" => "!importData.loading"} + %h4 Data Archives + .mt-5{"ng-if" => "user"} + %label.normal.inline{"ng-if" => "user"} + %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"} + Encrypted + %label.normal.inline + %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"} + Decrypted + + %a.block{"ng-click" => "downloadDataArchive()"} Download Data Archive + + %label.block.mt-5 + %input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "ctrl.importFileSelected(files)"} + .fake-link Import Data from 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"} @@ -58,4 +63,4 @@ .spinner{"ng-if" => "importData.loading"} - %a{"ng-click" => "destroyLocalData()"} Destroy all local data + %a.block.mt-25.red{"ng-click" => "destroyLocalData()"} Destroy all local data