keymanager, syncmanager
This commit is contained in:
@@ -20,9 +20,6 @@ angular.module('app.frontend', [
|
|||||||
.config(function (RestangularProvider, apiControllerProvider) {
|
.config(function (RestangularProvider, apiControllerProvider) {
|
||||||
RestangularProvider.setDefaultHeaders({"Content-Type": "application/json"});
|
RestangularProvider.setDefaultHeaders({"Content-Type": "application/json"});
|
||||||
|
|
||||||
var url = apiControllerProvider.defaultServerURL();
|
|
||||||
RestangularProvider.setBaseUrl(url + "/api");
|
|
||||||
|
|
||||||
RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
|
RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
|
||||||
var token = localStorage.getItem("jwt");
|
var token = localStorage.getItem("jwt");
|
||||||
if(token) {
|
if(token) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
angular.module('app.frontend')
|
angular.module('app.frontend')
|
||||||
.directive("header", function(apiController, extensionManager){
|
.directive("header", function(apiController){
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
scope: {},
|
scope: {},
|
||||||
@@ -16,15 +16,9 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.controller('HeaderCtrl', function ($state, apiController, modelManager, $timeout, extensionManager, dbManager) {
|
.controller('HeaderCtrl', function (apiController, modelManager, $timeout, dbManager) {
|
||||||
|
|
||||||
this.user = apiController.user;
|
this.user = apiController.user;
|
||||||
this.extensionManager = extensionManager;
|
|
||||||
this.loginData = {mergeLocal: true};
|
|
||||||
|
|
||||||
this.changePasswordPressed = function() {
|
|
||||||
this.showNewPasswordForm = !this.showNewPasswordForm;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.accountMenuPressed = function() {
|
this.accountMenuPressed = function() {
|
||||||
this.serverData = {url: apiController.getServer()};
|
this.serverData = {url: apiController.getServer()};
|
||||||
@@ -32,126 +26,19 @@ angular.module('app.frontend')
|
|||||||
this.showFaq = false;
|
this.showFaq = false;
|
||||||
this.showNewPasswordForm = false;
|
this.showNewPasswordForm = false;
|
||||||
this.showExtensionsMenu = false;
|
this.showExtensionsMenu = false;
|
||||||
|
this.showIOMenu = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toggleExtensions = function() {
|
this.toggleExtensions = function() {
|
||||||
this.showAccountMenu = false;
|
this.showAccountMenu = false;
|
||||||
|
this.showIOMenu = false;
|
||||||
this.showExtensionsMenu = !this.showExtensionsMenu;
|
this.showExtensionsMenu = !this.showExtensionsMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toggleExtensionForm = function() {
|
this.toggleIO = function() {
|
||||||
this.newExtensionData = {};
|
this.showIOMenu = !this.showIOMenu;
|
||||||
this.showNewExtensionForm = !this.showNewExtensionForm;
|
this.showExtensionsMenu = false;
|
||||||
}
|
|
||||||
|
|
||||||
this.submitNewExtensionForm = function() {
|
|
||||||
if(this.newExtensionData.url) {
|
|
||||||
extensionManager.addExtension(this.newExtensionData.url, function(response){
|
|
||||||
if(!response) {
|
|
||||||
alert("Unable to register this extension. Make sure the link is valid and try again.");
|
|
||||||
} else {
|
|
||||||
this.newExtensionData.url = "";
|
|
||||||
this.showNewExtensionForm = false;
|
|
||||||
}
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectedAction = function(action, extension) {
|
|
||||||
action.running = true;
|
|
||||||
extensionManager.executeAction(action, extension, null, function(response){
|
|
||||||
action.running = false;
|
|
||||||
if(response && response.error) {
|
|
||||||
action.error = true;
|
|
||||||
alert("There was an error performing this action. Please try again.");
|
|
||||||
} else {
|
|
||||||
action.error = false;
|
|
||||||
apiController.sync(null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.syncProviderActionIsEnabled = function(action) {
|
|
||||||
var provider = apiController.syncProviderForURL(action.url);
|
|
||||||
if(!provider) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return provider.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.enableSyncProvider = function(action, extension, primary) {
|
|
||||||
if(extension.encrypted && !extension.ek) {
|
|
||||||
alert("You must set an encryption key for this extension before enabling this action.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var provider = apiController.findOrCreateSyncProviderForUrl(action.url);
|
|
||||||
provider.primary = primary;
|
|
||||||
provider.enabled = true;
|
|
||||||
provider.ek = extension.ek;
|
|
||||||
apiController.addSyncProvider(provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.disableSyncProvider = function(action, extension) {
|
|
||||||
apiController.removeSyncProvider(apiController.syncProviderForURL(action.url));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.deleteExtension = function(extension) {
|
|
||||||
if(confirm("Are you sure you want to delete this extension?")) {
|
|
||||||
extensionManager.deleteExtension(extension);
|
|
||||||
var syncProviderAction = extension.syncProviderAction;
|
|
||||||
if(syncProviderAction) {
|
|
||||||
apiController.removeSyncProvider(apiController.syncProviderForURL(syncProviderAction.url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reloadExtensionsPressed = function() {
|
|
||||||
if(confirm("For your security, reloading extensions will disable any currently enabled sync providers and repeat actions.")) {
|
|
||||||
extensionManager.refreshExtensionsFromServer();
|
|
||||||
var syncProviderAction = extension.syncProviderAction;
|
|
||||||
if(syncProviderAction) {
|
|
||||||
apiController.removeSyncProvider(apiController.syncProviderForURL(syncProviderAction.url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeServer = function() {
|
|
||||||
apiController.setServer(this.serverData.url, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.signOutPressed = function() {
|
|
||||||
this.showAccountMenu = false;
|
this.showAccountMenu = false;
|
||||||
apiController.signout(function(){
|
|
||||||
window.location.reload();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.submitPasswordChange = function() {
|
|
||||||
this.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(this.passwordChangeData.current_password, this.passwordChangeData.new_password, function(response){
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.localNotesCount = function() {
|
|
||||||
return modelManager.filteredNotes.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.mergeLocalChanged = function() {
|
|
||||||
if(!this.loginData.mergeLocal) {
|
|
||||||
if(!confirm("Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?")) {
|
|
||||||
this.loginData.mergeLocal = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refreshData = function() {
|
this.refreshData = function() {
|
||||||
@@ -171,110 +58,4 @@ angular.module('app.frontend')
|
|||||||
this.syncUpdated = function() {
|
this.syncUpdated = function() {
|
||||||
this.lastSyncDate = new Date();
|
this.lastSyncDate = new Date();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
this.loginSubmitPressed = function() {
|
|
||||||
this.loginData.status = "Generating Login Keys...";
|
|
||||||
|
|
||||||
$timeout(function(){
|
|
||||||
apiController.login(this.loginData.email, this.loginData.user_password, function(response){
|
|
||||||
if(!response || response.error) {
|
|
||||||
var error = response ? response.error : {message: "An unknown error occured."}
|
|
||||||
this.loginData.status = null;
|
|
||||||
if(!response || (response && !response.didDisplayAlert)) {
|
|
||||||
alert(error.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.onAuthSuccess(response.user);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.submitRegistrationForm = function() {
|
|
||||||
this.loginData.status = "Generating Account Keys...";
|
|
||||||
|
|
||||||
$timeout(function(){
|
|
||||||
apiController.register(this.loginData.email, this.loginData.user_password, function(response){
|
|
||||||
if(!response || response.error) {
|
|
||||||
var error = response ? response.error : {message: "An unknown error occured."}
|
|
||||||
this.loginData.status = null;
|
|
||||||
alert(error.message);
|
|
||||||
} else {
|
|
||||||
this.onAuthSuccess(response.user);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.encryptionStatusForNotes = function() {
|
|
||||||
var allNotes = modelManager.filteredNotes;
|
|
||||||
return allNotes.length + "/" + allNotes.length + " notes encrypted";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.archiveEncryptionFormat = {encrypted: true};
|
|
||||||
|
|
||||||
this.downloadDataArchive = function() {
|
|
||||||
var link = document.createElement('a');
|
|
||||||
link.setAttribute('download', 'notes.json');
|
|
||||||
link.href = apiController.itemsDataFile(this.archiveEncryptionFormat.encrypted);
|
|
||||||
link.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.performImport = function(data, password) {
|
|
||||||
this.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);
|
|
||||||
this.importData.loading = false;
|
|
||||||
if(success) {
|
|
||||||
this.importData = null;
|
|
||||||
} else {
|
|
||||||
alert("There was an error importing your data. Please try again.");
|
|
||||||
}
|
|
||||||
}.bind(this))
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.submitImportPassword = function() {
|
|
||||||
this.performImport(this.importData.data, this.importData.password);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.importFileSelected = function(files) {
|
|
||||||
this.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
|
|
||||||
this.importData.requestPassword = true;
|
|
||||||
this.importData.data = data;
|
|
||||||
} else {
|
|
||||||
this.performImport(data, null);
|
|
||||||
}
|
|
||||||
}.bind(this))
|
|
||||||
}.bind(this)
|
|
||||||
|
|
||||||
reader.readAsText(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onAuthSuccess = function(user) {
|
|
||||||
var block = function(){
|
|
||||||
window.location.reload();
|
|
||||||
this.showLogin = false;
|
|
||||||
this.showRegistration = false;
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
if(!this.loginData.mergeLocal) {
|
|
||||||
dbManager.clearAllItems(function(){
|
|
||||||
block();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
block();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class Extension extends Item {
|
|||||||
this.content_type = "Extension";
|
this.content_type = "Extension";
|
||||||
}
|
}
|
||||||
|
|
||||||
syncProviderAction() {
|
get syncProviderAction() {
|
||||||
return _.find(this.actions, {sync_provider: true})
|
return _.find(this.actions, {sync_provider: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
class SyncProvider {
|
class SyncProvider {
|
||||||
constructor(obj) {
|
constructor(obj) {
|
||||||
|
this.encrypted = true;
|
||||||
_.merge(this, obj);
|
_.merge(this, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,8 +29,9 @@ class SyncProvider {
|
|||||||
return {
|
return {
|
||||||
enabled: this.enabled,
|
enabled: this.enabled,
|
||||||
url: this.url,
|
url: this.url,
|
||||||
encrypted: this.encrypted,
|
primary: this.primary,
|
||||||
ek: this.ek
|
keyName: this.keyName,
|
||||||
|
syncToken: this.syncToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,24 +7,11 @@ angular.module('app.frontend')
|
|||||||
return domain;
|
return domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url;
|
this.$get = function($rootScope, Restangular, modelManager, dbManager, keyManager, syncManager) {
|
||||||
|
return new ApiController($rootScope, Restangular, modelManager, dbManager, keyManager, syncManager);
|
||||||
this.defaultServerURL = function() {
|
|
||||||
if(!url) {
|
|
||||||
url = localStorage.getItem("server");
|
|
||||||
if(!url) {
|
|
||||||
url = "https://n3.standardnotes.org";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ApiController($rootScope, Restangular, modelManager, dbManager, keyManager, syncManager) {
|
||||||
this.$get = function($rootScope, Restangular, modelManager, dbManager) {
|
|
||||||
return new ApiController($rootScope, Restangular, modelManager, dbManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ApiController($rootScope, Restangular, modelManager, dbManager) {
|
|
||||||
|
|
||||||
var userData = localStorage.getItem("user");
|
var userData = localStorage.getItem("user");
|
||||||
if(userData) {
|
if(userData) {
|
||||||
@@ -36,35 +23,15 @@ angular.module('app.frontend')
|
|||||||
this.user = {uuid: idData};
|
this.user = {uuid: idData};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.syncToken = localStorage.getItem("syncToken");
|
|
||||||
|
|
||||||
/*
|
|
||||||
Config
|
|
||||||
*/
|
|
||||||
|
|
||||||
this.getServer = function() {
|
|
||||||
if(!url) {
|
|
||||||
url = localStorage.getItem("server");
|
|
||||||
if(!url) {
|
|
||||||
url = "https://n3.standardnotes.org";
|
|
||||||
this.setServer(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setServer = function(url, refresh) {
|
|
||||||
localStorage.setItem("server", url);
|
|
||||||
if(refresh) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Auth
|
Auth
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
this.defaultServerURL = function() {
|
||||||
|
return localStorage.getItem("server") || "https://n3.standardnotes.org";
|
||||||
|
}
|
||||||
|
|
||||||
this.getAuthParams = function() {
|
this.getAuthParams = function() {
|
||||||
return JSON.parse(localStorage.getItem("auth_params"));
|
return JSON.parse(localStorage.getItem("auth_params"));
|
||||||
}
|
}
|
||||||
@@ -73,8 +40,9 @@ angular.module('app.frontend')
|
|||||||
return localStorage.getItem("jwt");
|
return localStorage.getItem("jwt");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getAuthParamsForEmail = function(email, callback) {
|
this.getAuthParamsForEmail = function(url, email, callback) {
|
||||||
var request = Restangular.one("auth", "params");
|
var requestUrl = url + "/auth/params";
|
||||||
|
var request = Restangular.oneUrl(requestUrl, requestUrl);
|
||||||
request.get({email: email}).then(function(response){
|
request.get({email: email}).then(function(response){
|
||||||
callback(response.plain());
|
callback(response.plain());
|
||||||
})
|
})
|
||||||
@@ -96,10 +64,10 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.login = function(email, password, callback) {
|
this.login = function(url, email, password, callback) {
|
||||||
this.getAuthParamsForEmail(email, function(authParams){
|
this.getAuthParamsForEmail(url, email, function(authParams){
|
||||||
if(!authParams) {
|
if(!authParams) {
|
||||||
callback(null);
|
callback({error: "Unable to get authentication parameters."});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,36 +81,44 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
|
|
||||||
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){
|
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){
|
||||||
this.setMk(keys.mk);
|
var mk = keys.mk;
|
||||||
var request = Restangular.one("auth/sign_in");
|
var requestUrl = url + "/auth/sign_in";
|
||||||
|
var request = Restangular.oneUrl(requestUrl, requestUrl);
|
||||||
var params = {password: keys.pw, email: email};
|
var params = {password: keys.pw, email: email};
|
||||||
_.merge(request, params);
|
_.merge(request, params);
|
||||||
request.post().then(function(response){
|
request.post().then(function(response){
|
||||||
|
localStorage.setItem("server", url);
|
||||||
localStorage.setItem("jwt", response.token);
|
localStorage.setItem("jwt", response.token);
|
||||||
localStorage.setItem("user", JSON.stringify(response.user));
|
localStorage.setItem("user", JSON.stringify(response.user));
|
||||||
localStorage.setItem("auth_params", JSON.stringify(authParams));
|
localStorage.setItem("auth_params", JSON.stringify(authParams));
|
||||||
|
keyManager.addKey(SNKeyName, mk);
|
||||||
|
this.addStandardFileSyncProvider(url);
|
||||||
callback(response);
|
callback(response);
|
||||||
})
|
}.bind(this))
|
||||||
.catch(function(response){
|
.catch(function(response){
|
||||||
|
console.log("Error logging in", response);
|
||||||
callback(response.data);
|
callback(response.data);
|
||||||
})
|
})
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.register = function(email, password, callback) {
|
this.register = function(url, email, password, callback) {
|
||||||
Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){
|
Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){
|
||||||
this.setMk(keys.mk);
|
var mk = keys.mk;
|
||||||
keys.mk = null;
|
var requestUrl = url + "/auth";
|
||||||
var request = Restangular.one("auth");
|
var request = Restangular.oneUrl(requestUrl, requestUrl);
|
||||||
var params = _.merge({password: keys.pw, email: email}, authParams);
|
var params = _.merge({password: keys.pw, email: email}, authParams);
|
||||||
_.merge(request, params);
|
_.merge(request, params);
|
||||||
request.post().then(function(response){
|
request.post().then(function(response){
|
||||||
|
localStorage.setItem("server", url);
|
||||||
localStorage.setItem("jwt", response.token);
|
localStorage.setItem("jwt", response.token);
|
||||||
localStorage.setItem("user", JSON.stringify(response.user));
|
localStorage.setItem("user", JSON.stringify(response.user));
|
||||||
localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"])));
|
localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"])));
|
||||||
|
keyManager.addKey(SNKeyName, mk);
|
||||||
|
this.addStandardFileSyncProvider(url);
|
||||||
callback(response);
|
callback(response);
|
||||||
})
|
}.bind(this))
|
||||||
.catch(function(response){
|
.catch(function(response){
|
||||||
callback(response.data);
|
callback(response.data);
|
||||||
})
|
})
|
||||||
@@ -190,8 +166,9 @@ angular.module('app.frontend')
|
|||||||
// }.bind(this));
|
// }.bind(this));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
this._performPasswordChange = function(email, current_keys, new_keys, callback) {
|
this._performPasswordChange = function(url, email, current_keys, new_keys, callback) {
|
||||||
var request = Restangular.one("auth");
|
var requestUrl = url + "/auth";
|
||||||
|
var request = Restangular.oneUrl(requestUrl, requestUrl);
|
||||||
var params = {password: new_keys.pw, password_confirmation: new_keys.pw, current_password: current_keys.pw, email: email};
|
var params = {password: new_keys.pw, password_confirmation: new_keys.pw, current_password: current_keys.pw, email: email};
|
||||||
_.merge(request, params);
|
_.merge(request, params);
|
||||||
request.patch().then(function(response){
|
request.patch().then(function(response){
|
||||||
@@ -204,73 +181,6 @@ angular.module('app.frontend')
|
|||||||
Sync
|
Sync
|
||||||
*/
|
*/
|
||||||
|
|
||||||
this.syncProviderForURL = function(url) {
|
|
||||||
return _.find(this.syncProviders, {url: url});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.findOrCreateSyncProviderForUrl = function(url) {
|
|
||||||
var provider = _.find(this.syncProviders, {url: url});
|
|
||||||
if(!provider) {
|
|
||||||
provider = new SyncProvider({url: url})
|
|
||||||
}
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setEncryptionStatusForProviderURL = function(providerURL, encrypted) {
|
|
||||||
this.providerForURL(providerURL).encrypted = encrypted;
|
|
||||||
this.persistSyncProviders();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadSyncProviders = function() {
|
|
||||||
var providers = [];
|
|
||||||
var saved = localStorage.getItem("syncProviders");
|
|
||||||
if(saved) {
|
|
||||||
var parsed = JSON.parse(saved);
|
|
||||||
for(var p of parsed) {
|
|
||||||
providers.push(new SyncProvider(p));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no providers saved, use default
|
|
||||||
if(this.isUserSignedIn()) {
|
|
||||||
var defaultProvider = new SyncProvider(this.getServer() + "/items/sync", true);
|
|
||||||
providers.push(defaultProvider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.syncProviders = providers;
|
|
||||||
}
|
|
||||||
this.loadSyncProviders();
|
|
||||||
|
|
||||||
this.addSyncProvider = function(syncProvider) {
|
|
||||||
if(syncProvider.primary) {
|
|
||||||
for(var provider of this.syncProviders) {
|
|
||||||
provider.primary = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// since we're adding a new provider, we need to send it EVERYTHING we have now.
|
|
||||||
syncProvider.addPendingItems(modelManager.allItems);
|
|
||||||
|
|
||||||
this.syncProviders.push(syncProvider);
|
|
||||||
this.persistSyncProviders();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeSyncProvider = function(provider) {
|
|
||||||
_.pull(this.syncProviders, provider);
|
|
||||||
this.persistSyncProviders();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.persistSyncProviders = function() {
|
|
||||||
localStorage.setItem("syncProviders", JSON.stringify(_.map(this.syncProviders, function(provider) {
|
|
||||||
return provider.asJSON()
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setSyncToken = function(syncToken) {
|
|
||||||
this.syncToken = syncToken;
|
|
||||||
localStorage.setItem("syncToken", this.syncToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.syncWithOptions = function(callback, options = {}) {
|
this.syncWithOptions = function(callback, options = {}) {
|
||||||
|
|
||||||
var allDirtyItems = modelManager.getDirtyItems();
|
var allDirtyItems = modelManager.getDirtyItems();
|
||||||
@@ -306,7 +216,7 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(let provider of this.syncProviders) {
|
for(let provider of this.syncProviders) {
|
||||||
if(provider.enabled == false) {
|
if(!provider.enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
provider.addPendingItems(allDirtyItems);
|
provider.addPendingItems(allDirtyItems);
|
||||||
@@ -354,18 +264,17 @@ angular.module('app.frontend')
|
|||||||
return this.paramsForItem(item, provider.encrypted, provider.ek, options.additionalFields, false);
|
return this.paramsForItem(item, provider.encrypted, provider.ek, options.additionalFields, false);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
if(provider.primary) {
|
request.sync_token = provider.syncToken;
|
||||||
// only primary providers receive items (or care about received items)
|
request.cursor_token = provider.cursorToken;
|
||||||
request.sync_token = this.syncToken;
|
|
||||||
request.cursor_token = provider.cursorToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.post().then(function(response) {
|
request.post().then(function(response) {
|
||||||
|
|
||||||
|
console.log("Sync completion", response);
|
||||||
|
|
||||||
|
provider.syncToken = response.sync_token;
|
||||||
|
|
||||||
if(provider.primary) {
|
if(provider.primary) {
|
||||||
// handle sync token
|
$rootScope.$broadcast("sync:updated_token", provider.syncToken);
|
||||||
this.setSyncToken(response.sync_token);
|
|
||||||
$rootScope.$broadcast("sync:updated_token", this.syncToken);
|
|
||||||
|
|
||||||
// handle cursor token (more results waiting, perform another sync)
|
// handle cursor token (more results waiting, perform another sync)
|
||||||
provider.cursorToken = response.cursor_token;
|
provider.cursorToken = response.cursor_token;
|
||||||
@@ -382,6 +291,7 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
|
|
||||||
provider.syncOpInProgress = false;
|
provider.syncOpInProgress = false;
|
||||||
|
this.didMakeChangesToSyncProviders();
|
||||||
|
|
||||||
if(provider.cursorToken || provider.repeatOnCompletion == true) {
|
if(provider.cursorToken || provider.repeatOnCompletion == true) {
|
||||||
this.__performSyncWithProvider(provider, options, callback);
|
this.__performSyncWithProvider(provider, options, callback);
|
||||||
@@ -434,12 +344,13 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.handleItemsResponse = function(responseItems, omitFields, syncProvider) {
|
this.handleItemsResponse = function(responseItems, omitFields, syncProvider) {
|
||||||
this.decryptItemsWithKey(responseItems, syncProvider ? syncProvider.ek : null);
|
var ek = syncProvider ? keyManager.keyForName(syncProvider.keyName).key : null;
|
||||||
|
this.decryptItemsWithKey(responseItems, ek);
|
||||||
return modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields);
|
return modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.paramsForExportFile = function(item, ek, encrypted) {
|
this.paramsForExportFile = function(item, ek, encrypted) {
|
||||||
return _.omit(this.paramsForItem(item, encrypted, ["created_at", "updated_at"], true), ["deleted"]);
|
return _.omit(this.paramsForItem(item, encrypted, ek, ["created_at", "updated_at"], true), ["deleted"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.paramsForExtension = function(item, encrypted) {
|
this.paramsForExtension = function(item, encrypted) {
|
||||||
@@ -482,8 +393,10 @@ angular.module('app.frontend')
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
this.clearSyncToken = function() {
|
this.clearSyncToken = function() {
|
||||||
this.syncToken = null;
|
var primary = this.primarySyncProvider();
|
||||||
localStorage.removeItem("syncToken");
|
if(primary) {
|
||||||
|
primary.syncToken = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.importJSONData = function(data, password, callback) {
|
this.importJSONData = function(data, password, callback) {
|
||||||
@@ -526,7 +439,7 @@ angular.module('app.frontend')
|
|||||||
Export
|
Export
|
||||||
*/
|
*/
|
||||||
|
|
||||||
this.itemsDataFile = function(encrypted) {
|
this.itemsDataFile = function(encrypted, custom_ek) {
|
||||||
var textFile = null;
|
var textFile = null;
|
||||||
var makeTextFile = function (text) {
|
var makeTextFile = function (text) {
|
||||||
var data = new Blob([text], {type: 'text/json'});
|
var data = new Blob([text], {type: 'text/json'});
|
||||||
@@ -543,15 +456,21 @@ angular.module('app.frontend')
|
|||||||
return textFile;
|
return textFile;
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
|
var ek = custom_ek;
|
||||||
|
if(encrypted && !custom_ek) {
|
||||||
|
ek = this.retrieveMk();
|
||||||
|
}
|
||||||
|
|
||||||
var items = _.map(modelManager.allItemsMatchingTypes(["Tag", "Note"]), function(item){
|
var items = _.map(modelManager.allItemsMatchingTypes(["Tag", "Note"]), function(item){
|
||||||
return this.paramsForExportFile(item, encrypted);
|
return this.paramsForExportFile(item, ek, encrypted);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
items: items
|
items: items
|
||||||
}
|
}
|
||||||
|
|
||||||
if(encrypted) {
|
if(encrypted && !custom_ek) {
|
||||||
|
// auth params are only needed when encrypted with a standard file key
|
||||||
data["auth_params"] = this.getAuthParams();
|
data["auth_params"] = this.getAuthParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,7 +535,8 @@ angular.module('app.frontend')
|
|||||||
localStorage.setItem('mk', mk);
|
localStorage.setItem('mk', mk);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.signout = function(callback) {
|
this.signoutOfStandardFile = function(callback) {
|
||||||
|
this.removeStandardFileSyncProvider();
|
||||||
dbManager.clearAllItems(function(){
|
dbManager.clearAllItems(function(){
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
callback();
|
callback();
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
class AccountDataMenu {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.restrict = "E";
|
||||||
|
this.templateUrl = "frontend/directives/account-data-menu.html";
|
||||||
|
this.scope = {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
controller($scope, apiController, modelManager) {
|
||||||
|
'ngInject';
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').directive('accountDataMenu', () => new AccountDataMenu);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
class AccountKeysSection {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.restrict = "E";
|
||||||
|
this.templateUrl = "frontend/directives/account-keys-section.html";
|
||||||
|
this.scope = {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
controller($scope, apiController, keyManager) {
|
||||||
|
'ngInject';
|
||||||
|
|
||||||
|
$scope.newKeyData = {};
|
||||||
|
$scope.keys = keyManager.keys;
|
||||||
|
|
||||||
|
$scope.submitNewKeyForm = function() {
|
||||||
|
keyManager.addKey($scope.newKeyData.name, $scope.newKeyData.key);
|
||||||
|
$scope.newKeyData.showForm = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').directive('accountKeysSection', () => new AccountKeysSection);
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
class AccountSyncSection {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.restrict = "E";
|
||||||
|
this.templateUrl = "frontend/directives/account-sync-section.html";
|
||||||
|
this.scope = {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
controller($scope, apiController, modelManager, keyManager) {
|
||||||
|
'ngInject';
|
||||||
|
|
||||||
|
$scope.syncProviders = apiController.syncProviders;
|
||||||
|
$scope.newSyncData = {showAddSyncForm: false}
|
||||||
|
$scope.keys = keyManager.keys;
|
||||||
|
|
||||||
|
$scope.submitExternalSyncURL = function() {
|
||||||
|
apiController.addSyncProviderFromURL($scope.newSyncData.url);
|
||||||
|
$scope.newSyncData.showAddSyncForm = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.enableSyncProvider = function(provider, primary) {
|
||||||
|
if(!provider.keyName) {
|
||||||
|
alert("You must choose an encryption key for this provider before enabling it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
apiController.enableSyncProvider(provider, primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.removeSyncProvider = function(provider) {
|
||||||
|
apiController.removeSyncProvider(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').directive('accountSyncSection', () => new AccountSyncSection);
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
class AccountVendorAccountSection {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.restrict = "E";
|
||||||
|
this.templateUrl = "frontend/directives/account-vendor-account-section.html";
|
||||||
|
this.scope = {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
controller($scope, apiController, modelManager, $timeout, dbManager) {
|
||||||
|
'ngInject';
|
||||||
|
|
||||||
|
$scope.loginData = {mergeLocal: true, url: apiController.defaultServerURL()};
|
||||||
|
$scope.user = apiController.user;
|
||||||
|
|
||||||
|
$scope.changePasswordPressed = function() {
|
||||||
|
$scope.showNewPasswordForm = !$scope.showNewPasswordForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.signOutPressed = function() {
|
||||||
|
$scope.showAccountMenu = false;
|
||||||
|
apiController.signoutOfStandardFile(function(){
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$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.localNotesCount = function() {
|
||||||
|
return modelManager.filteredNotes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.mergeLocalChanged = function() {
|
||||||
|
if(!$scope.loginData.mergeLocal) {
|
||||||
|
if(!confirm("Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?")) {
|
||||||
|
$scope.loginData.mergeLocal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.loginSubmitPressed = function() {
|
||||||
|
$scope.loginData.status = "Generating Login Keys...";
|
||||||
|
console.log("logging in with url", $scope.loginData.url);
|
||||||
|
$timeout(function(){
|
||||||
|
apiController.login($scope.loginData.url, $scope.loginData.email, $scope.loginData.user_password, function(response){
|
||||||
|
if(!response || response.error) {
|
||||||
|
var error = response ? response.error : {message: "An unknown error occured."}
|
||||||
|
$scope.loginData.status = null;
|
||||||
|
if(!response || (response && !response.didDisplayAlert)) {
|
||||||
|
alert(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$scope.onAuthSuccess(response.user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.submitRegistrationForm = function() {
|
||||||
|
$scope.loginData.status = "Generating Account Keys...";
|
||||||
|
|
||||||
|
$timeout(function(){
|
||||||
|
apiController.register($scope.loginData.url, $scope.loginData.email, $scope.loginData.user_password, function(response){
|
||||||
|
if(!response || response.error) {
|
||||||
|
var error = response ? response.error : {message: "An unknown error occured."}
|
||||||
|
$scope.loginData.status = null;
|
||||||
|
alert(error.message);
|
||||||
|
} else {
|
||||||
|
$scope.onAuthSuccess(response.user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.encryptionStatusForNotes = function() {
|
||||||
|
var allNotes = modelManager.filteredNotes;
|
||||||
|
return allNotes.length + "/" + allNotes.length + " notes encrypted";
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.onAuthSuccess = function(user) {
|
||||||
|
var block = function(){
|
||||||
|
window.location.reload();
|
||||||
|
$scope.showLogin = false;
|
||||||
|
$scope.showRegistration = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!$scope.loginData.mergeLocal) {
|
||||||
|
dbManager.clearAllItems(function(){
|
||||||
|
block();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').directive('accountVendorAccountSection', () => new AccountVendorAccountSection);
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
class GlobalExtensionsMenu {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.restrict = "E";
|
||||||
|
this.templateUrl = "frontend/directives/global-extensions-menu.html";
|
||||||
|
this.scope = {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
controller($scope, apiController, extensionManager) {
|
||||||
|
'ngInject';
|
||||||
|
|
||||||
|
$scope.extensionManager = extensionManager;
|
||||||
|
|
||||||
|
$scope.toggleExtensionForm = function() {
|
||||||
|
$scope.newExtensionData = {};
|
||||||
|
$scope.showNewExtensionForm = !$scope.showNewExtensionForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.submitNewExtensionForm = function() {
|
||||||
|
if($scope.newExtensionData.url) {
|
||||||
|
extensionManager.addExtension($scope.newExtensionData.url, function(response){
|
||||||
|
if(!response) {
|
||||||
|
alert("Unable to register this extension. Make sure the link is valid and try again.");
|
||||||
|
} else {
|
||||||
|
$scope.newExtensionData.url = "";
|
||||||
|
$scope.showNewExtensionForm = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.selectedAction = function(action, extension) {
|
||||||
|
action.running = true;
|
||||||
|
extensionManager.executeAction(action, extension, null, function(response){
|
||||||
|
action.running = false;
|
||||||
|
if(response && response.error) {
|
||||||
|
action.error = true;
|
||||||
|
alert("There was an error performing this action. Please try again.");
|
||||||
|
} else {
|
||||||
|
action.error = false;
|
||||||
|
apiController.sync(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.changeExtensionEncryptionFormat = function(encrypted, extension) {
|
||||||
|
var provider = $scope.syncProviderForExtension(extension);
|
||||||
|
if(provider) {
|
||||||
|
if(confirm("Changing encryption status will update all your items and re-sync them back to the server. This can take several minutes. Are you sure you want to continue?")) {
|
||||||
|
extensionManager.changeExtensionEncryptionFormat(encrypted, extension);
|
||||||
|
apiController.resyncAllDataForProvider(provider);
|
||||||
|
} else {
|
||||||
|
// revert setting
|
||||||
|
console.log("reverting");
|
||||||
|
extension.encrypted = extensionManager.extensionUsesEncryptedData(extension);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
extensionManager.changeExtensionEncryptionFormat(encrypted, extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.deleteExtension = function(extension) {
|
||||||
|
if(confirm("Are you sure you want to delete this extension?")) {
|
||||||
|
extensionManager.deleteExtension(extension);
|
||||||
|
var syncProviderAction = extension.syncProviderAction;
|
||||||
|
if(syncProviderAction) {
|
||||||
|
apiController.removeSyncProvider(apiController.syncProviderForURL(syncProviderAction.url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.reloadExtensionsPressed = function() {
|
||||||
|
if(confirm("For your security, reloading extensions will disable any currently enabled sync providers and repeat actions.")) {
|
||||||
|
extensionManager.refreshExtensionsFromServer();
|
||||||
|
var syncProviderAction = extension.syncProviderAction;
|
||||||
|
if(syncProviderAction) {
|
||||||
|
apiController.removeSyncProvider(apiController.syncProviderForURL(syncProviderAction.url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.setEncryptionKeyForExtension = function(extension) {
|
||||||
|
extension.formData.changingKey = false;
|
||||||
|
var ek = extension.formData.ek;
|
||||||
|
extensionManager.setEkForExtension(extension, ek);
|
||||||
|
if(extension.formData.changingKey) {
|
||||||
|
var syncAction = extension.syncProviderAction;
|
||||||
|
if(syncAction) {
|
||||||
|
var provider = apiController.syncProviderForURL(syncAction.url);
|
||||||
|
provider.ek = ek;
|
||||||
|
apiController.didMakeChangesToSyncProviders();
|
||||||
|
apiController.resyncAllDataForProvider(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.changeEncryptionKeyPressed = function(extension) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension.formData.changingKey = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').directive('globalExtensionsMenu', () => new GlobalExtensionsMenu);
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
class ImportExportMenu {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.restrict = "E";
|
||||||
|
this.templateUrl = "frontend/directives/import-export-menu.html";
|
||||||
|
this.scope = {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
controller($scope, apiController, $timeout) {
|
||||||
|
'ngInject';
|
||||||
|
|
||||||
|
$scope.archiveFormData = {encryption_type: $scope.user ? 'mk' : 'ek'};
|
||||||
|
$scope.user = apiController.user;
|
||||||
|
|
||||||
|
$scope.downloadDataArchive = function() {
|
||||||
|
if(!apiController.isUserSignedIn() && $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('importExportMenu', () => new ImportExportMenu);
|
||||||
@@ -6,6 +6,7 @@ class ExtensionManager {
|
|||||||
this.apiController = apiController;
|
this.apiController = apiController;
|
||||||
this.enabledRepeatActionUrls = JSON.parse(localStorage.getItem("enabledRepeatActionUrls")) || [];
|
this.enabledRepeatActionUrls = JSON.parse(localStorage.getItem("enabledRepeatActionUrls")) || [];
|
||||||
this.decryptedExtensions = JSON.parse(localStorage.getItem("decryptedExtensions")) || [];
|
this.decryptedExtensions = JSON.parse(localStorage.getItem("decryptedExtensions")) || [];
|
||||||
|
this.extensionEks = JSON.parse(localStorage.getItem("extensionEks")) || {};
|
||||||
|
|
||||||
modelManager.addItemSyncObserver("extensionManager", "Extension", function(items){
|
modelManager.addItemSyncObserver("extensionManager", "Extension", function(items){
|
||||||
for (var ext of items) {
|
for (var ext of items) {
|
||||||
@@ -31,6 +32,15 @@ class ExtensionManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ekForExtension(extension) {
|
||||||
|
return this.extensionEks[extension.url];
|
||||||
|
}
|
||||||
|
|
||||||
|
setEkForExtension(extension, ek) {
|
||||||
|
this.extensionEks[extension.url] = ek;
|
||||||
|
localStorage.setItem("extensionEks", JSON.stringify(this.extensionEks));
|
||||||
|
}
|
||||||
|
|
||||||
actionWithURL(url) {
|
actionWithURL(url) {
|
||||||
for (var extension of this.extensions) {
|
for (var extension of this.extensions) {
|
||||||
return _.find(extension.actions, {url: url})
|
return _.find(extension.actions, {url: url})
|
||||||
@@ -42,6 +52,7 @@ class ExtensionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
changeExtensionEncryptionFormat(encrypted, extension) {
|
changeExtensionEncryptionFormat(encrypted, extension) {
|
||||||
|
console.log("changing encryption status");
|
||||||
if(encrypted) {
|
if(encrypted) {
|
||||||
_.pull(this.decryptedExtensions, extension.url);
|
_.pull(this.decryptedExtensions, extension.url);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
26
app/assets/javascripts/app/services/keyManager.js
Normal file
26
app/assets/javascripts/app/services/keyManager.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
class KeyManager {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.keys = JSON.parse(localStorage.getItem("keys")) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
addKey(name, key) {
|
||||||
|
this.keys.push({name: name, key: key});
|
||||||
|
this.persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
keyForName(name) {
|
||||||
|
return _.find(this.keys, {name: name});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteKey(name) {
|
||||||
|
_.pull(this.keys, {name: name});
|
||||||
|
this.persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
persist() {
|
||||||
|
localStorage.setItem("keys", JSON.stringify(this.keys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').service('keyManager', KeyManager);
|
||||||
111
app/assets/javascripts/app/services/syncManager.js
Normal file
111
app/assets/javascripts/app/services/syncManager.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
class SyncManager {
|
||||||
|
|
||||||
|
let SNKeyName = "Standard Notes Key";
|
||||||
|
|
||||||
|
constructor(modelManager) {
|
||||||
|
this.modelManager = modelManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
primarySyncProvider() {
|
||||||
|
return _.find(this.syncProviders, {primary: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeStandardFileSyncProvider() {
|
||||||
|
var sfProvider = _.find(this.syncProviders, {url: this.defaultServerURL() + "/items/sync"})
|
||||||
|
_.pull(this.syncProviders, sfProvider);
|
||||||
|
this.didMakeChangesToSyncProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
addStandardFileSyncProvider(url) {
|
||||||
|
var defaultProvider = new SyncProvider({url: url + "/items/sync", primary: this.syncProviders.length == 0});
|
||||||
|
defaultProvider.keyName = SNKeyName;
|
||||||
|
defaultProvider.enabled = this.syncProviders.length == 0;
|
||||||
|
this.syncProviders.push(defaultProvider);
|
||||||
|
return defaultProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, use default
|
||||||
|
if(this.isUserSignedIn()) {
|
||||||
|
var defaultProvider = this.addStandardFileSyncProvider(this.defaultServerURL());
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
this.didMakeChangesToSyncProviders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadSyncProviders();
|
||||||
|
|
||||||
|
addSyncProviderFromURL(url) {
|
||||||
|
var provider = new SyncProvider({url: url});
|
||||||
|
this.syncProviders.push(provider);
|
||||||
|
this.didMakeChangesToSyncProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
enableSyncProvider(syncProvider, primary) {
|
||||||
|
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.
|
||||||
|
syncProvider.addPendingItems(this.modelManager.allItems);
|
||||||
|
this.didMakeChangesToSyncProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
resyncAllDataForProvider(syncProvider) {
|
||||||
|
syncProvider.addPendingItems(this.modelManager.allItems);
|
||||||
|
this.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSyncProvider(provider) {
|
||||||
|
_.pull(this.syncProviders, provider);
|
||||||
|
this.didMakeChangesToSyncProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').service('syncManager', SyncManager);
|
||||||
@@ -64,9 +64,207 @@
|
|||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Extensions
|
||||||
|
*/
|
||||||
|
|
||||||
|
.extensions-panel {
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.extension-link {
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $blue-color !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-form {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registered-extensions {
|
||||||
|
|
||||||
|
.extension {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border: 1px solid #f2f2f2;
|
||||||
|
padding: 14px 6px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
.ek-input-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 14px;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
> input {
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid rgba(gray, 0.15);
|
||||||
|
padding: 5px;
|
||||||
|
width: 78%;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button {
|
||||||
|
width: 20% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
display: inline-block !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.option-link {
|
||||||
|
margin-top: 6px;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-ek {
|
||||||
|
> .ek {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .disclaimer {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
width: 60%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $blue-color !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-name {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.encryption-format {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> .title {
|
||||||
|
font-size: 13px;
|
||||||
|
// font-weight: bold;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-actions {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.action {
|
||||||
|
padding: 13px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: rgba(white, 0.9);
|
||||||
|
border: 1px solid rgba(gray, 0.15);
|
||||||
|
|
||||||
|
.action-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-permissions {
|
||||||
|
margin-top: 2px;
|
||||||
|
a {
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .execute-type {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .error {
|
||||||
|
color: red;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .last-run {
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 11px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-data-menu {
|
||||||
|
padding: 5px !important;
|
||||||
|
|
||||||
|
.providers {
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.provider {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border: 1px solid #f2f2f2;
|
||||||
|
padding: 10px 10px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
> .type {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .url {
|
||||||
|
// white-space: nowrap;
|
||||||
|
// text-overflow: ellipsis;
|
||||||
|
// overflow: hidden;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .options {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-keys-section {
|
||||||
|
.keys {
|
||||||
|
.key {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border: 1px solid #f2f2f2;
|
||||||
|
padding: 10px 10px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
|
||||||
|
> .name {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .value {
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
a {
|
a {
|
||||||
color: $dark-gray;
|
color: $dark-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
@@ -50,50 +51,99 @@
|
|||||||
.login-panel .login-input {
|
.login-panel .login-input {
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.items {
|
.items {
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 7px;
|
margin-right: 7px;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #515263;
|
color: #515263;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
box-shadow: 0px 0px 15px rgba(black, 0.2);
|
box-shadow: 0px 0px 15px rgba(black, 0.2);
|
||||||
border: none;
|
border: none;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
max-height: 85vh;
|
max-height: 85vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
background-color: white;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $blue-color;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.light {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 30px;
|
||||||
|
padding-top: 3px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
font-weight: normal;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid rgba(gray, 0.15);
|
||||||
|
cursor: pointer;
|
||||||
|
color: $blue-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
.storage-text {
|
background-color: rgba(gray, 0.10);
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox {
|
.execution-spinner {
|
||||||
font-size: 14px;
|
|
||||||
font-weight: normal;
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
.storage-text {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.io {
|
||||||
|
.enc-option {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.half-button {
|
.half-button {
|
||||||
@@ -127,11 +177,7 @@
|
|||||||
|
|
||||||
.link-item {
|
.link-item {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
a {
|
|
||||||
font-size: 12px;
|
|
||||||
color: $blue-color;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@@ -157,17 +203,12 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .icon-container {
|
.meta-container {
|
||||||
display: block;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .meta-container {
|
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .action-container {
|
.action-container {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
|
|
||||||
@@ -209,16 +250,6 @@
|
|||||||
margin-bottom: 8px !important;
|
margin-bottom: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .icon-container {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
.icon {
|
|
||||||
height: 35px;
|
|
||||||
&.archive {
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-container {
|
.meta-container {
|
||||||
> .title {
|
> .title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -273,132 +304,3 @@ a.disabled {
|
|||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Extensions
|
|
||||||
*/
|
|
||||||
|
|
||||||
.extensions-panel {
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
.extension-link {
|
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $blue-color !important;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.extension-form {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.registered-extensions {
|
|
||||||
|
|
||||||
|
|
||||||
.extension {
|
|
||||||
margin-bottom: 18px;
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
border: 1px solid #f2f2f2;
|
|
||||||
padding: 14px 6px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $blue-color !important;
|
|
||||||
font-size: 12px !important;
|
|
||||||
font-weight: bold !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extension-name {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.encryption-format {
|
|
||||||
margin-top: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
> .title {
|
|
||||||
font-size: 13px;
|
|
||||||
// font-weight: bold;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.extension-subtitle {
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.extension-actions {
|
|
||||||
margin-top: 15px;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
.action {
|
|
||||||
padding: 13px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
background-color: rgba(white, 0.9);
|
|
||||||
border: 1px solid rgba(gray, 0.15);
|
|
||||||
|
|
||||||
.action-name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-permissions {
|
|
||||||
margin-top: 2px;
|
|
||||||
a {
|
|
||||||
font-weight: normal !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.execute {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
font-size: 12px;
|
|
||||||
height: 30px;
|
|
||||||
padding-top: 7px;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 6px;
|
|
||||||
border: 1px solid rgba(gray, 0.15);
|
|
||||||
cursor: pointer;
|
|
||||||
color: $blue-color;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(gray, 0.10);
|
|
||||||
}
|
|
||||||
|
|
||||||
.execution-spinner {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .execute-type {
|
|
||||||
font-size: 12px;
|
|
||||||
margin-bottom: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .error {
|
|
||||||
color: red;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .last-run {
|
|
||||||
opacity: 0.5;
|
|
||||||
font-size: 11px;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.panel.panel-default.account-panel.panel-right.account-data-menu
|
||||||
|
.panel-body
|
||||||
|
.account-items
|
||||||
|
%section.account-item
|
||||||
|
%h3{"ng-click" => "showSN = !showSN"} Standard Notes Account
|
||||||
|
%account-vendor-account-section{"ng-if" => "showSN"}
|
||||||
|
|
||||||
|
%section.account-item
|
||||||
|
%h3{"ng-click" => "showSync = !showSync"} Sync Providers
|
||||||
|
%account-sync-section{"ng-if" => "showSync"}
|
||||||
|
|
||||||
|
%section.account-item
|
||||||
|
%h3{"ng-click" => "showKeys = !showKeys"} Encryption Keys
|
||||||
|
%account-keys-section{"ng-if" => "showKeys"}
|
||||||
|
|
||||||
|
%section.account-item
|
||||||
|
%h3{"ng-click" => "showIO = !showIO"} Import/Export
|
||||||
|
%import-export-menu{"ng-if" => "showIO"}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
.account-keys-section
|
||||||
|
.keys
|
||||||
|
.key{"ng-repeat" => "key in keys"}
|
||||||
|
.name {{key.name}}
|
||||||
|
.value {{key.key}}
|
||||||
|
|
||||||
|
%a{"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
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.providers
|
||||||
|
.provider{"ng-repeat" => "provider in syncProviders"}
|
||||||
|
.type {{provider.primary == null ? 'Not enabled' : (provider.primary ? 'Primary' : 'Secondary')}}
|
||||||
|
.key{"ng-if" => "provider.keyName"} Using key: {{provider.keyName}}
|
||||||
|
.url {{provider.url}}
|
||||||
|
.options
|
||||||
|
%div{"ng-if" => "!provider.enabled"}
|
||||||
|
%strong Choose encryption key:
|
||||||
|
%select{"ng-model" => "provider.keyName"}
|
||||||
|
%option{"ng-repeat" => "key in keys", "ng-selected" => "{{key.name == provider.keyName}}", "value" => "{{key.name}}"}
|
||||||
|
{{key.name}}
|
||||||
|
%button.light{"ng-click" => "enableSyncProvider(provider, true)"} Enable as Primary sync provider
|
||||||
|
%button.light{"ng-click" => "enableSyncProvider(provider, false)"} Enable as Secondary sync provider
|
||||||
|
%button.light{"ng-click" => "removeSyncProvider(provider)"} Remove Provider
|
||||||
|
%button.light{"ng-if" => "provider.keyName", "ng-click" => "changeEncryptionKey(provider)"} Change Encryption Key
|
||||||
|
|
||||||
|
%a{"ng-click" => "newSyncData.showAddSyncForm = !newSyncData.showAddSyncForm"} Add external sync with Secret URL
|
||||||
|
%form.sync-form{"ng-if" => "newSyncData.showAddSyncForm"}
|
||||||
|
.form-tag.has-feedback
|
||||||
|
%input.form-control{:autofocus => 'autofocus', :name => 'url', :placeholder => 'Secret URL', :required => true, :type => 'url', 'ng-model' => 'newSyncData.url'}
|
||||||
|
%button.btn.dark-button.btn-block{"ng-click" => "submitExternalSyncURL()"}
|
||||||
|
Add External Sync
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
.registration-login
|
||||||
|
%div{"ng-if" => "user"}
|
||||||
|
.email {{user.email}}
|
||||||
|
.server {{serverURL}}
|
||||||
|
.links{"ng-if" => "user"}
|
||||||
|
.link-item
|
||||||
|
%a{"ng-click" => "signOutPressed()"} Sign Out
|
||||||
|
.meta-container
|
||||||
|
.title Local Encryption
|
||||||
|
.desc Notes are encrypted locally before being sent to the server. Neither the server owner nor an intrusive entity can decrypt your locally encrypted notes.
|
||||||
|
.action-container
|
||||||
|
%span.status-title Status:
|
||||||
|
{{encryptionStatusForNotes()}}
|
||||||
|
%div{"ng-if" => "!user"}
|
||||||
|
.meta-container
|
||||||
|
.title Sign in or Register (optional)
|
||||||
|
.desc Enter your <a href="https://standardfile.org" target="_blank">Standard File</a> account information.
|
||||||
|
.action-container
|
||||||
|
%form.account-form{'name' => "loginForm"}
|
||||||
|
.form-tag.has-feedback
|
||||||
|
%input.form-control{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'loginData.url'}
|
||||||
|
.form-tag.has-feedback
|
||||||
|
%input.form-control.login-input{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'loginData.email'}
|
||||||
|
.form-tag.has-feedback
|
||||||
|
%input.form-control.login-input{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'loginData.user_password'}
|
||||||
|
.checkbox{"ng-if" => "localNotesCount() > 0"}
|
||||||
|
%label
|
||||||
|
%input{"type" => "checkbox", "ng-model" => "loginData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"}
|
||||||
|
Merge local notes ({{localNotesCount()}} notes)
|
||||||
|
%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
|
||||||
|
.login-forgot{"style" => "padding-top: 4px;"}
|
||||||
|
%a.btn.btn-link{"ng-click" => "showResetForm = !showResetForm"} Passwords cannot be forgotten.
|
||||||
|
.panel-status-text{"ng-if" => "loginData.status", "style" => "font-size: 14px;"} {{loginData.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 <strong>must</strong> make sure to store or remember your password.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
.panel.panel-default.account-panel.panel-right.extensions-panel
|
||||||
|
.panel-body
|
||||||
|
%div{"style" => "font-size: 18px;", "ng-if" => "!extensionManager.extensions.length"} No extensions installed
|
||||||
|
.registered-extensions{"ng-if" => "extensionManager.extensions.length"}
|
||||||
|
.extension{"ng-repeat" => "extension in extensionManager.extensions", "ng-init" => "extension.formData = {}"}
|
||||||
|
.extension-name {{extension.name}}
|
||||||
|
.encryption-format
|
||||||
|
.title Send data:
|
||||||
|
%label
|
||||||
|
%input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "true", "ng-change" => "changeExtensionEncryptionFormat(true, extension)"}
|
||||||
|
Encrypted
|
||||||
|
%label
|
||||||
|
%input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "false", "ng-change" => "changeExtensionEncryptionFormat(false, extension)"}
|
||||||
|
Decrypted
|
||||||
|
.ek-input-wrapper{"ng-if" => "extension.encrypted && (!extensionManager.ekForExtension(extension) || extension.formData.changingKey)"}
|
||||||
|
%input{"ng-model" => "extension.formData.ek", "placeholder" => "Set encryption key"}
|
||||||
|
%button.light{"ng-click" => "setEncryptionKeyForExtension(extension)"} Set
|
||||||
|
.extension-actions
|
||||||
|
.action{"ng-repeat" => "action in extension.actionsInGlobalContext()"}
|
||||||
|
%div{"ng-if" => "!action.sync_provider"}
|
||||||
|
.action-name {{action.label}}
|
||||||
|
.action-desc{"style" => "font-style: italic;"} {{action.desc}}
|
||||||
|
.execute-type{"ng-if" => "action.repeat_mode == 'watch'"}
|
||||||
|
Repeats when a change is made to your items.
|
||||||
|
.execute-type{"ng-if" => "action.repeat_mode == 'loop'"}
|
||||||
|
Repeats at most once every {{action.repeat_timeout}} seconds
|
||||||
|
.action-permissions
|
||||||
|
%a{"ng-click" => "action.showPermissions = !action.showPermissions"} {{action.showPermissions ? "Hide permissions" : "Show permissions"}}
|
||||||
|
%div{"ng-if" => "action.showPermissions"}
|
||||||
|
{{action.permissionsString}}
|
||||||
|
.encryption-type
|
||||||
|
%span {{action.encryptionModeString}}
|
||||||
|
%div{"ng-if" => "action.sync_provider"}
|
||||||
|
.action-name This is a sync provider action.
|
||||||
|
.action-desc{"style" => "margin-top: -5px;"}
|
||||||
|
%p Enabling this sync provider as a primary provider will replace your current sync provider.
|
||||||
|
%p Enabling this sync provider as a secondary provider will save your data with this provider as a backup, but will not be used to pull changes.
|
||||||
|
%p You can have only one primary provider, and multiple secondary providers.
|
||||||
|
|
||||||
|
%div{"ng-if" => "!syncProviderActionIsEnabled(action)"}
|
||||||
|
%button.light.execute{"ng-click" => "enableSyncProvider(action, extension, true)"} Enable as primary sync provider
|
||||||
|
%button.light.execute{"ng-click" => "enableSyncProvider(action, extension, false)"} Enable as backup sync provider
|
||||||
|
%div{"ng-if" => "syncProviderActionIsEnabled(action)"}
|
||||||
|
%button.light.execute{"ng-click" => "disableSyncProvider(action, extension)"} Remove as sync provider
|
||||||
|
|
||||||
|
.execute{"ng-if" => "!action.sync_provider"}
|
||||||
|
%div{"ng-if" => "action.repeat_mode"}
|
||||||
|
%div{"ng-if" => "extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.disableRepeatAction(action, extension)"} Disable
|
||||||
|
%div{"ng-if" => "!extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.enableRepeatAction(action, extension)"} Enable
|
||||||
|
%div{"ng-if" => "!action.repeat_mode", "ng-click" => "selectedAction(action, extension)"}
|
||||||
|
%div{"ng-if" => "!action.running"}
|
||||||
|
Perform Action
|
||||||
|
%div{"ng-if" => "action.running"}
|
||||||
|
.spinner.execution-spinner
|
||||||
|
.last-run{"ng-if" => "!action.error && action.lastExecuted && !action.running"}
|
||||||
|
Last run {{action.lastExecuted | appDateTime}}
|
||||||
|
.error{"ng-if" => "action.error"}
|
||||||
|
Error performing action.
|
||||||
|
|
||||||
|
|
||||||
|
%a.option-link{"ng-if" => "extension.encrypted && extensionManager.ekForExtension(extension) && !extension.formData.changingKey", "ng-click" => "extension.formData.showEk = !extension.formData.showEk"}
|
||||||
|
Show Encryption Key
|
||||||
|
.show-ek{"style" => "text-align: center", "ng-if" => "extension.formData.showEk"}
|
||||||
|
.ek {{extensionManager.ekForExtension(extension)}}
|
||||||
|
.disclaimer This key is saved locally and never sent to any servers.
|
||||||
|
%a.option-link{"ng-if" => "extension.encrypted && extensionManager.ekForExtension(extension) && !extension.formData.changingKey", "ng-click" => "changeEncryptionKeyPressed(extension)"}
|
||||||
|
Change Encryption Key
|
||||||
|
%a.option-link{"ng-click" => "deleteExtension(extension)"} Remove extension
|
||||||
|
|
||||||
|
.extension-link
|
||||||
|
%a{"ng-click" => "toggleExtensionForm()"} Add new extension
|
||||||
|
|
||||||
|
%form.extension-form{"ng-if" => "showNewExtensionForm"}
|
||||||
|
.form-tag.has-feedback
|
||||||
|
%input.form-control{:autofocus => 'autofocus', :name => 'url', :placeholder => 'Extension URL', :required => true, :type => 'url', 'ng-model' => 'newExtensionData.url'}
|
||||||
|
%button.btn.dark-button.btn-block{"ng-click" => "submitNewExtensionForm()", :type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
|
||||||
|
%span.ladda-label Add Extension
|
||||||
|
|
||||||
|
.extension-link
|
||||||
|
%a{"ng-click" => "reloadExtensionsPressed()", "ng-if" => "extensionManager.extensions.length > 0"} Reload all extensions
|
||||||
|
.extension-link
|
||||||
|
%a{"href" => "https://standardnotes.org/extensions", "target" => "_blank"} List of available extensions
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
.options{"style" => "font-size: 12px; margin-top: 4px;"}
|
||||||
|
%label.enc-option{"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.enc-option
|
||||||
|
%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.enc-option
|
||||||
|
%input{"type" => "radio", "ng-model" => "archiveFormData.encryption_type", "ng-value" => "'none'", "ng-change" => "archiveFormData.encryption_type = 'none'"}
|
||||||
|
Decrypted
|
||||||
|
.action-container
|
||||||
|
%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
|
||||||
|
.import-password{"ng-if" => "importData.requestPassword"}
|
||||||
|
Enter the account password associated with the import file.
|
||||||
|
%input.field{"type" => "text", "ng-model" => "importData.password"}
|
||||||
|
%button{"ng-click" => "submitImportPassword()"} Decrypt & Import
|
||||||
|
.spinner{"ng-if" => "importData.loading"}
|
||||||
@@ -1,175 +1,16 @@
|
|||||||
.header
|
.header
|
||||||
.header-content
|
.header-content
|
||||||
.menu.left
|
.menu.left
|
||||||
|
|
||||||
.items
|
.items
|
||||||
.item.account
|
.item.account
|
||||||
%div{"ng-click" => "ctrl.accountMenuPressed()"}
|
%a{"ng-click" => "ctrl.accountMenuPressed()"} Data
|
||||||
%div{"ng-if" => "ctrl.user"} Account
|
%account-data-menu
|
||||||
%div{"ng-if" => "!ctrl.user"} Sign in or Register
|
-# {"ng-if" => "ctrl.showAccountMenu"}
|
||||||
.panel.panel-default.account-panel.panel-right{"ng-if" => "ctrl.showAccountMenu"}
|
|
||||||
.panel-body
|
|
||||||
.account-items
|
|
||||||
.account-item.registration-login{"ng-if" => "!ctrl.user"}
|
|
||||||
.account-item
|
|
||||||
.meta-container
|
|
||||||
.title Server
|
|
||||||
.desc Enter your <a href="https://standardfile.org" target="_blank">Standard File</a> server address, or use the default.
|
|
||||||
.action-container
|
|
||||||
%form.account-form{'ng-submit' => 'ctrl.changeServer()', 'name' => "serverChangeForm"}
|
|
||||||
.form-tag.has-feedback
|
|
||||||
%input.form-control{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'ctrl.serverData.url'}
|
|
||||||
%button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
|
|
||||||
%span.ladda-label Set Server
|
|
||||||
.meta-container
|
|
||||||
.title Sign in or Register
|
|
||||||
.desc
|
|
||||||
%form.account-form{'name' => "loginForm"}
|
|
||||||
.form-tag.has-feedback
|
|
||||||
%input.form-control.login-input{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'ctrl.loginData.email'}
|
|
||||||
.form-tag.has-feedback
|
|
||||||
%input.form-control.login-input{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.loginData.user_password'}
|
|
||||||
.checkbox{"ng-if" => "ctrl.localNotesCount() > 0"}
|
|
||||||
%label
|
|
||||||
%input{"type" => "checkbox", "ng-model" => "ctrl.loginData.mergeLocal", "ng-bind" => "true", "ng-change" => "ctrl.mergeLocalChanged()"}
|
|
||||||
Merge local notes ({{ctrl.localNotesCount()}} notes)
|
|
||||||
%button.btn.dark-button.half-button{"ng-click" => "ctrl.loginSubmitPressed()", "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
|
|
||||||
%span Sign In
|
|
||||||
%button.btn.dark-button.half-button{"ng-click" => "ctrl.submitRegistrationForm()", "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
|
|
||||||
%span Register
|
|
||||||
%br
|
|
||||||
.login-forgot{"style" => "padding-top: 4px;"}
|
|
||||||
%a.btn.btn-link{"ng-click" => "ctrl.showResetForm = !ctrl.showResetForm"} Passwords cannot be forgotten.
|
|
||||||
.panel-status-text{"ng-if" => "ctrl.loginData.status", "style" => "font-size: 14px;"} {{ctrl.loginData.status}}
|
|
||||||
|
|
||||||
%div{"ng-if" => "ctrl.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 <strong>must</strong> make sure to store or remember your password.
|
|
||||||
|
|
||||||
.account-item{"ng-if" => "ctrl.user"}
|
|
||||||
.email {{ctrl.user.email}}
|
|
||||||
.server {{ctrl.serverData.url}}
|
|
||||||
.links{"ng-if" => "ctrl.user"}
|
|
||||||
-# .link-item
|
|
||||||
-# %a{"ng-click" => "ctrl.changePasswordPressed()"} Change Password
|
|
||||||
-# %form.account-form{"ng-if" => "ctrl.showNewPasswordForm", 'ng-submit' => 'ctrl.submitPasswordChange()', 'name' => "passwordChangeForm"}
|
|
||||||
-# .form-tag.has-feedback
|
|
||||||
-# %input.form-control.login-input{:autofocus => 'autofocus', :name => 'current', :placeholder => 'Current password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.current_password'}
|
|
||||||
-# .form-tag.has-feedback
|
|
||||||
-# %input.form-control.login-input{:placeholder => 'New password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password', "autocomplete" => "new-password"}
|
|
||||||
-# .form-tag.has-feedback
|
|
||||||
-# %input.form-control.login-input{:placeholder => 'Confirm password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'ctrl.passwordChangeData.new_password_confirmation', "autocomplete" => "new-password"}
|
|
||||||
-# %button.btn.dark-button.btn-block{:type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
|
|
||||||
-# %span.ladda-label Change Password
|
|
||||||
-# .panel-status-text{"ng-if" => "ctrl.passwordChangeData.status", "style" => "font-size: 14px;"}
|
|
||||||
-# {{ctrl.passwordChangeData.status}}
|
|
||||||
.link-item
|
|
||||||
%a{"ng-click" => "ctrl.signOutPressed()"} Sign Out
|
|
||||||
.meta-container
|
|
||||||
.title Local Encryption
|
|
||||||
.desc Notes are encrypted locally before being sent to the server. Neither the server owner nor an intrusive entity can decrypt your locally encrypted notes.
|
|
||||||
.action-container
|
|
||||||
%span.status-title Status:
|
|
||||||
{{ctrl.encryptionStatusForNotes()}}
|
|
||||||
.account-item{"ng-if" => "ctrl.user"}
|
|
||||||
.meta-container
|
|
||||||
.title Data Archives
|
|
||||||
.options{"style" => "font-size: 12px; margin-top: 4px;"}
|
|
||||||
%label
|
|
||||||
%input{"type" => "radio", "ng-model" => "ctrl.archiveEncryptionFormat.encrypted", "ng-value" => "true", "ng-change" => "ctrl.archiveEncryptionFormat.encrypted = true"}
|
|
||||||
Encrypted
|
|
||||||
%label
|
|
||||||
%input{"type" => "radio", "ng-model" => "ctrl.archiveEncryptionFormat.encrypted", "ng-value" => "false", "ng-change" => "ctrl.archiveEncryptionFormat.encrypted = false"}
|
|
||||||
Decrypted
|
|
||||||
.action-container
|
|
||||||
%a{"ng-click" => "ctrl.downloadDataArchive()"} Download Data Archive
|
|
||||||
%br
|
|
||||||
%div{"ng-if" => "!ctrl.importData.loading"}
|
|
||||||
%label#import-archive
|
|
||||||
%input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "ctrl.importFileSelected(files)"}
|
|
||||||
%a.disabled
|
|
||||||
%span
|
|
||||||
Import Data from Archive
|
|
||||||
.import-password{"ng-if" => "ctrl.importData.requestPassword"}
|
|
||||||
Enter the account password associated with the import file.
|
|
||||||
%input.field{"type" => "text", "ng-model" => "ctrl.importData.password"}
|
|
||||||
%button{"ng-click" => "ctrl.submitImportPassword()"} Decrypt & Import
|
|
||||||
.spinner{"ng-if" => "ctrl.importData.loading"}
|
|
||||||
|
|
||||||
.item
|
.item
|
||||||
%a{"ng-click" => "ctrl.toggleExtensions()"} Extensions
|
%a{"ng-click" => "ctrl.toggleExtensions()"} Extensions
|
||||||
.panel.panel-default.account-panel.panel-right.extensions-panel{"ng-if" => "ctrl.showExtensionsMenu"}
|
%global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"}
|
||||||
.panel-body
|
|
||||||
%div{"style" => "font-size: 18px;", "ng-if" => "!ctrl.extensionManager.extensions.length"} No extensions installed
|
|
||||||
.registered-extensions{"ng-if" => "ctrl.extensionManager.extensions.length"}
|
|
||||||
.extension{"ng-repeat" => "extension in ctrl.extensionManager.extensions"}
|
|
||||||
.extension-name {{extension.name}}
|
|
||||||
.encryption-format
|
|
||||||
.title Send data:
|
|
||||||
%label
|
|
||||||
%input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "true", "ng-change" => "ctrl.extensionManager.changeExtensionEncryptionFormat(true, extension)"}
|
|
||||||
Encrypted
|
|
||||||
%label
|
|
||||||
%input{"type" => "radio", "ng-model" => "extension.encrypted", "ng-value" => "false", "ng-change" => "ctrl.extensionManager.changeExtensionEncryptionFormat(false, extension)"}
|
|
||||||
Decrypted
|
|
||||||
.encryption-key{"ng-if" => "extension.encrypted"}
|
|
||||||
%input{"ng-model" => "extension.ek", "placeholder" => "Set encryption key"}
|
|
||||||
.extension-actions
|
|
||||||
.action{"ng-repeat" => "action in extension.actionsInGlobalContext()"}
|
|
||||||
%div{"ng-if" => "!action.sync_provider"}
|
|
||||||
.action-name {{action.label}}
|
|
||||||
.action-desc{"style" => "font-style: italic;"} {{action.desc}}
|
|
||||||
.execute-type{"ng-if" => "action.repeat_mode == 'watch'"}
|
|
||||||
Repeats when a change is made to your items.
|
|
||||||
.execute-type{"ng-if" => "action.repeat_mode == 'loop'"}
|
|
||||||
Repeats at most once every {{action.repeat_timeout}} seconds
|
|
||||||
.action-permissions
|
|
||||||
%a{"ng-click" => "action.showPermissions = !action.showPermissions"} {{action.showPermissions ? "Hide permissions" : "Show permissions"}}
|
|
||||||
%div{"ng-if" => "action.showPermissions"}
|
|
||||||
{{action.permissionsString}}
|
|
||||||
.encryption-type
|
|
||||||
%span {{action.encryptionModeString}}
|
|
||||||
%div{"ng-if" => "action.sync_provider"}
|
|
||||||
.action-name This is a sync provider action.
|
|
||||||
.action-desc{"style" => "margin-top: -5px;"}
|
|
||||||
%p Enabling this sync provider as a primary provider will replace your current sync provider.
|
|
||||||
%p Enabling this sync provider as a secondary provider will save your data with this provider as a backup, but will not be used to pull changes.
|
|
||||||
%p You can have only one primary provider, and multiple secondary providers.
|
|
||||||
|
|
||||||
%div{"ng-if" => "!ctrl.syncProviderActionIsEnabled(action)"}
|
|
||||||
.execute{"ng-click" => "ctrl.enableSyncProvider(action, extension, true)"} Enable as primary sync provider
|
|
||||||
.execute{"ng-click" => "ctrl.enableSyncProvider(action, extension, false)"} Enable as backup sync provider
|
|
||||||
%div{"ng-if" => "ctrl.syncProviderActionIsEnabled(action)"}
|
|
||||||
.execute{"ng-click" => "ctrl.disableSyncProvider(action, extension)"} Remove as sync provider
|
|
||||||
|
|
||||||
.execute{"ng-if" => "!action.sync_provider"}
|
|
||||||
%div{"ng-if" => "action.repeat_mode"}
|
|
||||||
%div{"ng-if" => "ctrl.extensionManager.isRepeatActionEnabled(action)", "ng-click" => "ctrl.extensionManager.disableRepeatAction(action, extension)"} Disable
|
|
||||||
%div{"ng-if" => "!ctrl.extensionManager.isRepeatActionEnabled(action)", "ng-click" => "ctrl.extensionManager.enableRepeatAction(action, extension)"} Enable
|
|
||||||
%div{"ng-if" => "!action.repeat_mode", "ng-click" => "ctrl.selectedAction(action, extension)"}
|
|
||||||
%div{"ng-if" => "!action.running"}
|
|
||||||
Perform Action
|
|
||||||
%div{"ng-if" => "action.running"}
|
|
||||||
.spinner.execution-spinner
|
|
||||||
.last-run{"ng-if" => "!action.error && action.lastExecuted && !action.running"}
|
|
||||||
Last run {{action.lastExecuted | appDateTime}}
|
|
||||||
.error{"ng-if" => "action.error"}
|
|
||||||
Error performing action.
|
|
||||||
%a{"ng-click" => "ctrl.deleteExtension(extension)", "style" => "margin-top: 22px; display: block; text-align: center;"} Remove extension
|
|
||||||
|
|
||||||
.extension-link
|
|
||||||
%a{"ng-click" => "ctrl.toggleExtensionForm()"} Add new extension
|
|
||||||
|
|
||||||
%form.extension-form{"ng-if" => "ctrl.showNewExtensionForm"}
|
|
||||||
.form-tag.has-feedback
|
|
||||||
%input.form-control{:autofocus => 'autofocus', :name => 'url', :placeholder => 'Extension URL', :required => true, :type => 'url', 'ng-model' => 'ctrl.newExtensionData.url'}
|
|
||||||
%button.btn.dark-button.btn-block{"ng-click" => "ctrl.submitNewExtensionForm()", :type => 'submit', "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
|
|
||||||
%span.ladda-label Add Extension
|
|
||||||
|
|
||||||
.extension-link
|
|
||||||
%a{"ng-click" => "ctrl.reloadExtensionsPressed()", "ng-if" => "ctrl.extensionManager.extensions.length > 0"} Reload all extensions
|
|
||||||
.extension-link
|
|
||||||
%a{"href" => "https://standardnotes.org/extensions", "target" => "_blank"} List of available extensions
|
|
||||||
|
|
||||||
.item
|
.item
|
||||||
%a{"href" => "https://standardnotes.org", "target" => "_blank"}
|
%a{"href" => "https://standardnotes.org", "target" => "_blank"}
|
||||||
|
|||||||
Reference in New Issue
Block a user