sync accounts
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var Neeto = Neeto || {};
|
||||
var SN = SN || {};
|
||||
|
||||
// detect IE8 and above, and edge.
|
||||
// IE and Edge do not support pbkdf2 in WebCrypto, therefore we need to use CryptoJS
|
||||
@@ -19,18 +20,4 @@ angular.module('app.frontend', [
|
||||
|
||||
.config(function (RestangularProvider, apiControllerProvider) {
|
||||
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
|
||||
};
|
||||
});
|
||||
})
|
||||
|
||||
@@ -19,9 +19,9 @@ angular.module('app.frontend')
|
||||
/**
|
||||
* Insert 4 spaces when a tab key is pressed,
|
||||
* only used when inside of the text editor.
|
||||
* If the shift key is pressed first, this event is
|
||||
* not fired.
|
||||
*/
|
||||
* If the shift key is pressed first, this event is
|
||||
* not fired.
|
||||
*/
|
||||
var handleTab = function (event) {
|
||||
if (!event.shiftKey && event.which == 9) {
|
||||
event.preventDefault();
|
||||
@@ -29,13 +29,13 @@ angular.module('app.frontend')
|
||||
var end = this.selectionEnd;
|
||||
var spaces = " ";
|
||||
|
||||
// Insert 4 spaces
|
||||
// Insert 4 spaces
|
||||
this.value = this.value.substring(0, start)
|
||||
+ spaces + this.value.substring(end);
|
||||
|
||||
// Place cursor 4 spaces away from where
|
||||
// the tab key was pressed
|
||||
this.selectionStart = this.selectionEnd = start + 4;
|
||||
// Place cursor 4 spaces away from where
|
||||
// the tab key was pressed
|
||||
this.selectionStart = this.selectionEnd = start + 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ angular.module('app.frontend')
|
||||
}
|
||||
}
|
||||
})
|
||||
.controller('EditorCtrl', function ($sce, $timeout, apiController, markdownRenderer, $rootScope, extensionManager) {
|
||||
.controller('EditorCtrl', function ($sce, $timeout, apiController, markdownRenderer, $rootScope, extensionManager, syncManager) {
|
||||
|
||||
this.setNote = function(note, oldNote) {
|
||||
this.editorMode = 'edit';
|
||||
@@ -148,12 +148,18 @@ angular.module('app.frontend')
|
||||
if(success) {
|
||||
if(statusTimeout) $timeout.cancel(statusTimeout);
|
||||
statusTimeout = $timeout(function(){
|
||||
this.noteStatus = "All changes saved"
|
||||
this.saveError = false;
|
||||
var status = "All changes saved"
|
||||
if(syncManager.offline) {
|
||||
status += " (offline)";
|
||||
}
|
||||
this.noteStatus = status;
|
||||
}.bind(this), 200)
|
||||
} else {
|
||||
if(statusTimeout) $timeout.cancel(statusTimeout);
|
||||
statusTimeout = $timeout(function(){
|
||||
this.noteStatus = "(Offline) — All changes saved"
|
||||
this.saveError = true;
|
||||
this.noteStatus = "Error saving"
|
||||
}.bind(this), 200)
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
@@ -12,6 +12,10 @@ angular.module('app.frontend')
|
||||
link:function(scope, elem, attrs, ctrl) {
|
||||
scope.$on("sync:updated_token", function(){
|
||||
ctrl.syncUpdated();
|
||||
ctrl.findErrors();
|
||||
})
|
||||
scope.$on("sync:error", function(){
|
||||
ctrl.findErrors();
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -19,9 +23,16 @@ angular.module('app.frontend')
|
||||
.controller('HeaderCtrl', function (apiController, modelManager, $timeout, dbManager, syncManager) {
|
||||
|
||||
this.user = apiController.user;
|
||||
this.offline = syncManager.offline;
|
||||
|
||||
this.findErrors = function() {
|
||||
this.error = syncManager.syncProviders.filter(function(provider){return provider.error}).length > 0 ? true : false;
|
||||
}
|
||||
this.findErrors();
|
||||
|
||||
|
||||
this.accountMenuPressed = function() {
|
||||
this.serverData = {url: syncManager.serverURL()};
|
||||
this.serverData = {};
|
||||
this.showAccountMenu = !this.showAccountMenu;
|
||||
this.showFaq = false;
|
||||
this.showNewPasswordForm = false;
|
||||
|
||||
@@ -7,11 +7,11 @@ angular.module('app.frontend')
|
||||
return domain;
|
||||
}
|
||||
|
||||
this.$get = function($rootScope, Restangular, modelManager, dbManager, keyManager, syncManager) {
|
||||
return new ApiController($rootScope, Restangular, modelManager, dbManager, keyManager, syncManager);
|
||||
this.$get = function($rootScope, Restangular, modelManager, dbManager, syncManager) {
|
||||
return new ApiController($rootScope, Restangular, modelManager, dbManager, syncManager);
|
||||
}
|
||||
|
||||
function ApiController($rootScope, Restangular, modelManager, dbManager, keyManager, syncManager) {
|
||||
function ApiController($rootScope, Restangular, modelManager, dbManager, syncManager) {
|
||||
|
||||
var userData = localStorage.getItem("user");
|
||||
if(userData) {
|
||||
@@ -79,7 +79,7 @@ angular.module('app.frontend')
|
||||
var params = {password: keys.pw, email: email};
|
||||
_.merge(request, params);
|
||||
request.post().then(function(response){
|
||||
this.handleAuthResponse(response, url, authParams, mk);
|
||||
this.handleAuthResponse(response, email, url, authParams, mk);
|
||||
callback(response);
|
||||
}.bind(this))
|
||||
.catch(function(response){
|
||||
@@ -90,13 +90,16 @@ angular.module('app.frontend')
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
this.handleAuthResponse = function(response, url, authParams, mk) {
|
||||
localStorage.setItem("server", url);
|
||||
localStorage.setItem("jwt", response.token);
|
||||
localStorage.setItem("user", JSON.stringify(response.user));
|
||||
localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"])));
|
||||
keyManager.addKey(SNKeyName, mk);
|
||||
syncManager.addStandardFileSyncProvider(url);
|
||||
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);
|
||||
}
|
||||
|
||||
this.register = function(url, email, password, callback) {
|
||||
@@ -107,7 +110,7 @@ angular.module('app.frontend')
|
||||
var params = _.merge({password: keys.pw, email: email}, authParams);
|
||||
_.merge(request, params);
|
||||
request.post().then(function(response){
|
||||
this.handleAuthResponse(response, url, authParams, mk);
|
||||
this.handleAuthResponse(response, email, url, authParams, mk);
|
||||
callback(response);
|
||||
}.bind(this))
|
||||
.catch(function(response){
|
||||
@@ -256,19 +259,6 @@ angular.module('app.frontend')
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
|
||||
this.signoutOfStandardFile = function(destroyAll, callback) {
|
||||
syncManager.removeStandardFileSyncProvider();
|
||||
if(destroyAll) {
|
||||
this.destroyLocalData(callback);
|
||||
} else {
|
||||
localStorage.removeItem("user");
|
||||
localStorage.removeItem("jwt");
|
||||
localStorage.removeItem("server");
|
||||
localStorage.removeItem("auth_params");
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
this.destroyLocalData = function(callback) {
|
||||
dbManager.clearAllItems(function(){
|
||||
localStorage.clear();
|
||||
|
||||
@@ -3,8 +3,7 @@ class AccountDataMenu {
|
||||
constructor() {
|
||||
this.restrict = "E";
|
||||
this.templateUrl = "frontend/directives/account-data-menu.html";
|
||||
this.scope = {
|
||||
};
|
||||
this.scope = {};
|
||||
}
|
||||
|
||||
controller($scope, apiController, modelManager, keyManager) {
|
||||
@@ -13,7 +12,7 @@ class AccountDataMenu {
|
||||
$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 providers, keys, and extensions.")) {
|
||||
if(!confirm("Are you sure you want to end your session? This will delete all local items, sync accounts, keys, and extensions.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class AccountNewAccountSection {
|
||||
controller($scope, apiController, modelManager, $timeout, dbManager, syncManager) {
|
||||
'ngInject';
|
||||
|
||||
$scope.formData = {mergeLocal: true, url: syncManager.serverURL()};
|
||||
$scope.formData = {mergeLocal: true, url: syncManager.defaultServerURL()};
|
||||
$scope.user = apiController.user;
|
||||
|
||||
$scope.showForm = syncManager.syncProviders.length == 0;
|
||||
@@ -20,15 +20,10 @@ class AccountNewAccountSection {
|
||||
}
|
||||
|
||||
$scope.submitExternalSyncURL = function() {
|
||||
syncManager.addSyncProviderFromURL($scope.newSyncData.url);
|
||||
$scope.newSyncData.showAddSyncForm = false;
|
||||
}
|
||||
|
||||
$scope.signOutPressed = function() {
|
||||
$scope.showAccountMenu = false;
|
||||
apiController.signoutOfStandardFile(false, function(){
|
||||
window.location.reload();
|
||||
})
|
||||
syncManager.addSyncProviderFromURL($scope.formData.secretUrl);
|
||||
$scope.formData.showAddLinkForm = false;
|
||||
$scope.formData.secretUrl = null;
|
||||
$scope.showForm = false;
|
||||
}
|
||||
|
||||
$scope.submitPasswordChange = function() {
|
||||
|
||||
@@ -16,7 +16,7 @@ class AccountSyncSection {
|
||||
|
||||
$scope.enableSyncProvider = function(provider, primary) {
|
||||
if(!provider.keyName) {
|
||||
alert("You must choose an encryption key for this provider before enabling it.");
|
||||
alert("You must choose an encryption key for this account before enabling it.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -24,19 +24,19 @@ class AccountSyncSection {
|
||||
}
|
||||
|
||||
$scope.removeSyncProvider = function(provider) {
|
||||
if(provider.isStandardNotesAccount) {
|
||||
alert("To remove your Standard Notes sync, sign out of your Standard Notes account.")
|
||||
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 provider?")) {
|
||||
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 Notes account, you need to change your password. However, this functionality is not currently supported.");
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ angular
|
||||
// Whenever the scope variable updates we simply
|
||||
// show if it evaluates to 'true' and hide if 'false'
|
||||
scope.$watch('show', function(newVal){
|
||||
console.log("show value changed", newVal);
|
||||
newVal ? showSpinner() : hideSpinner();
|
||||
});
|
||||
|
||||
@@ -29,7 +28,6 @@ angular
|
||||
}
|
||||
|
||||
function showElement(show) {
|
||||
console.log("show:", show);
|
||||
show ? elem.css({display:''}) : elem.css({display:'none'});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ export const SNKeyName = "Standard Notes Key";
|
||||
|
||||
class SyncManager {
|
||||
|
||||
constructor(modelManager, syncRunner) {
|
||||
constructor(modelManager, syncRunner, keyManager) {
|
||||
this.modelManager = modelManager;
|
||||
this.keyManager = keyManager;
|
||||
this.syncRunner = syncRunner;
|
||||
this.syncRunner.setOnChangeProviderCallback(function(){
|
||||
this.didMakeChangesToSyncProviders();
|
||||
@@ -15,14 +16,19 @@ class SyncManager {
|
||||
return this.enabledProviders.length == 0;
|
||||
}
|
||||
|
||||
serverURL() {
|
||||
return localStorage.getItem("server") || "https://n3.standardnotes.org";
|
||||
defaultServerURL() {
|
||||
return "https://n3.standardnotes.org";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -57,21 +63,6 @@ class SyncManager {
|
||||
return _.find(this.syncProviders, {primary: true});
|
||||
}
|
||||
|
||||
removeStandardFileSyncProvider() {
|
||||
var sfProvider = _.find(this.syncProviders, {url: this.serverURL() + "/items/sync"})
|
||||
_.pull(this.syncProviders, sfProvider);
|
||||
this.didMakeChangesToSyncProviders();
|
||||
}
|
||||
|
||||
addStandardFileSyncProvider(url) {
|
||||
var defaultProvider = new SyncProvider({url: url + "/items/sync", primary: !this.primarySyncProvider()});
|
||||
defaultProvider.keyName = SNKeyName;
|
||||
defaultProvider.enabled = this.syncProviders.length == 0;
|
||||
this.syncProviders.push(defaultProvider);
|
||||
this.didMakeChangesToSyncProviders();
|
||||
return defaultProvider;
|
||||
}
|
||||
|
||||
didMakeChangesToSyncProviders() {
|
||||
localStorage.setItem("syncProviders", JSON.stringify(_.map(this.syncProviders, function(provider) {
|
||||
return provider.asJSON()
|
||||
@@ -89,22 +80,58 @@ class SyncManager {
|
||||
} else {
|
||||
// no providers saved, this means migrating from old system to new
|
||||
// check if user is signed in
|
||||
if(this.offline && localStorage.getItem("user")) {
|
||||
var defaultProvider = this.addStandardFileSyncProvider(this.serverURL());
|
||||
defaultProvider.syncToken = localStorage.getItem("syncToken");
|
||||
// migrate old key structure to new
|
||||
var mk = localStorage.getItem("mk");
|
||||
if(mk) {
|
||||
keyManager.addKey(SNKeyName, mk);
|
||||
localStorage.removeItem("mk");
|
||||
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.didMakeChangesToSyncProviders();
|
||||
|
||||
this.keyManager.addKey(provider.keyName, ek);
|
||||
|
||||
if(this.syncProviders.length == 0) {
|
||||
this.enableSyncProvider(provider, true);
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
addSyncProviderFromURL(url) {
|
||||
var provider = new SyncProvider({url: url});
|
||||
provider.type = SN.SyncProviderType.URL;
|
||||
this.syncProviders.push(provider);
|
||||
this.didMakeChangesToSyncProviders();
|
||||
}
|
||||
@@ -123,8 +150,6 @@ class SyncManager {
|
||||
this.addAllDataAsNeedingSyncForProvider(syncProvider);
|
||||
this.didMakeChangesToSyncProviders();
|
||||
this.syncWithProvider(syncProvider);
|
||||
this.syncWithProvider(syncProvider);
|
||||
this.syncWithProvider(syncProvider);
|
||||
}
|
||||
|
||||
addAllDataAsNeedingSyncForProvider(syncProvider) {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
SN.SyncProviderType = {
|
||||
Account: 1,
|
||||
URL: 2
|
||||
}
|
||||
|
||||
class SyncProvider {
|
||||
|
||||
constructor(obj) {
|
||||
@@ -31,13 +36,28 @@ class SyncProvider {
|
||||
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
|
||||
syncToken: this.syncToken,
|
||||
|
||||
// account based
|
||||
email: this.email,
|
||||
uuid: this.uuid,
|
||||
jwt: this.jwt,
|
||||
auth_params: this.auth_params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ class SyncRunner {
|
||||
}
|
||||
|
||||
syncOffline(items, callback) {
|
||||
console.log("Writing items offline", items);
|
||||
this.writeItemsToLocalStorage(items, true, function(responseItems){
|
||||
// delete anything needing to be deleted
|
||||
for(var item of items) {
|
||||
@@ -128,7 +127,9 @@ class SyncRunner {
|
||||
request.sync_token = provider.syncToken;
|
||||
request.cursor_token = provider.cursorToken;
|
||||
|
||||
request.post().then(function(response) {
|
||||
var headers = provider.jwt ? {Authorization: "Bearer " + provider.jwt} : {};
|
||||
request.post("", undefined, undefined, headers).then(function(response) {
|
||||
provider.error = null;
|
||||
|
||||
if(!provider.primary) {
|
||||
console.log("Completed sync for provider:", provider.url, "Response:", response);
|
||||
@@ -170,15 +171,19 @@ class SyncRunner {
|
||||
}.bind(this))
|
||||
.catch(function(response){
|
||||
console.log("Sync error: ", response);
|
||||
var error = 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"});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mt-15 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.faded {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -74,11 +78,15 @@
|
||||
|
||||
section {
|
||||
padding: 5px;
|
||||
padding-bottom: 2px;
|
||||
margin-top: 5px;
|
||||
|
||||
&.inline-h {
|
||||
padding: 5px 0px;
|
||||
padding-top: 5px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
@@ -121,6 +129,10 @@
|
||||
.large-padding {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-bar-link {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
%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.url'}
|
||||
%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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
%div{"ng-if" => "showSection"}
|
||||
.small-v-space
|
||||
%section.white-bg{"ng-repeat" => "provider in syncProviders"}
|
||||
%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}}
|
||||
@@ -16,11 +16,15 @@
|
||||
{{key.name}}
|
||||
%button{"ng-click" => "saveKey(provider)"} Set
|
||||
|
||||
%div{"ng-if" => "!provider.enabled"}
|
||||
%button.light{"ng-click" => "enableSyncProvider(provider, true)"} Set as Main
|
||||
%button.light{"ng-if" => "syncProviders.length > 1", "ng-click" => "enableSyncProvider(provider, false)"} Add as Secondary
|
||||
%button.light{"ng-if" => "!provider.enabled || !provider.primary", "ng-click" => "enableSyncProvider(provider, true)"} Set as Main
|
||||
%button.light{"ng-if" => "syncProviders.length > 1 && (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 Provider
|
||||
%div{"style" => "height: 30px;", "delay-hide" => "true", "show" => "provider.syncOpInProgress", "delay" => "1000"}
|
||||
%strong{"style" => "float: left;"} Syncing: {{provider.syncStatus.statusString}}
|
||||
.spinner{"style" => "float: right"}
|
||||
%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}}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.footer-bar
|
||||
.pull-left
|
||||
.footer-bar-link
|
||||
%a{"ng-click" => "ctrl.accountMenuPressed()"} Account
|
||||
%a{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account
|
||||
%account-data-menu{"ng-if" => "ctrl.showAccountMenu"}
|
||||
|
||||
.footer-bar-link
|
||||
@@ -14,11 +14,12 @@
|
||||
|
||||
.pull-right
|
||||
|
||||
.footer-bar-link
|
||||
.footer-bar-link{"style" => "margin-right: 5px;"}
|
||||
%div{"ng-if" => "ctrl.lastSyncDate", "style" => "float: left; font-weight: normal; margin-right: 8px;"}
|
||||
%span{"ng-if" => "!ctrl.isRefreshing"}
|
||||
Last refreshed {{ctrl.lastSyncDate | appDateTime}}
|
||||
%span{"ng-if" => "ctrl.isRefreshing"}
|
||||
.spinner{"style" => "margin-top: 2px;"}
|
||||
|
||||
%a{"ng-click" => "ctrl.refreshData()"} Refresh
|
||||
%strong{"ng-if" => "ctrl.offline"} Offline
|
||||
%a{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} Refresh
|
||||
|
||||
Reference in New Issue
Block a user